From e59efa8041224b6a7c8c8feb2707624afc866937 Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 10:48:23 +0100 Subject: [PATCH 1/8] refactor: moved app functions into apps.cpp --- contracts/tonomy/build.sh | 2 +- contracts/tonomy/include/tonomy/apps.hpp | 241 ++++++++++++++++ contracts/tonomy/include/tonomy/tonomy.hpp | 204 +------------- contracts/tonomy/src/apps.cpp | 253 +++++++++++++++++ contracts/tonomy/src/tonomy.cpp | 305 +-------------------- 5 files changed, 505 insertions(+), 500 deletions(-) create mode 100644 contracts/tonomy/include/tonomy/apps.hpp create mode 100644 contracts/tonomy/src/apps.cpp diff --git a/contracts/tonomy/build.sh b/contracts/tonomy/build.sh index 1a69e7b..5d298b0 100755 --- a/contracts/tonomy/build.sh +++ b/contracts/tonomy/build.sh @@ -13,7 +13,7 @@ else WORKING_DIR="/contracts" fi -BUILD_COMMAND="cdt-cpp -abigen -I ${WORKING_DIR}/include -R ${WORKING_DIR}/ricardian -contract ${CONTRACT_NAME} -o ${WORKING_DIR}/${CONTRACT_NAME}.wasm ${WORKING_DIR}/src/${CONTRACT_NAME}.cpp ${WORKING_DIR}/src/native.cpp" +BUILD_COMMAND="cdt-cpp -abigen -I ${WORKING_DIR}/include -R ${WORKING_DIR}/ricardian -contract ${CONTRACT_NAME} -o ${WORKING_DIR}/${CONTRACT_NAME}.wasm ${WORKING_DIR}/src/${CONTRACT_NAME}.cpp ${WORKING_DIR}/src/apps.cpp ${WORKING_DIR}/src/native.cpp" echo $BUILD_COMMAND diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp new file mode 100644 index 0000000..c35c377 --- /dev/null +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -0,0 +1,241 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "native.hpp" + +namespace tonomysystem +{ + using eosio::action_wrapper; + using eosio::asset; + using eosio::check; + using eosio::checksum256; + using eosio::ignore; + using eosio::name; + using eosio::permission_level; + using eosio::print; + using eosio::public_key; + using eosio::singleton; + using std::string; + + using eosiotonomy::authority; + + /** + * The `eosio.tonomy` is the first sample of system contract provided by `block.one` through the EOSIO platform. It is a minimalist system contract because it only supplies the actions that are absolutely critical to bootstrap a chain and nothing more. This allows for a chain agnostic approach to bootstrapping a chain. + * + * Just like in the `eosio.system` sample contract implementation, there are a few actions which are not implemented at the contract level (`newaccount`, `updateauth`, `deleteauth`, `linkauth`, `unlinkauth`, `canceldelay`, `onerror`, `setabi`, `setcode`), they are just declared in the contract so they will show in the contract's ABI and users will be able to push those actions to the chain via the account holding the `eosio.system` contract, but the implementation is at the EOSIO core level. They are referred to as EOSIO native actions. + */ + class [[eosio::contract("tonomy")]] apps : public native + { + public: + static constexpr eosio::name app_controller_account = "gov.tmy"_n; + + uint64_t initial_cpu_weight_allocation = 1000; + uint64_t initial_net_weight_allocation = 1000; + + /** + * Constructor for the contract, which initializes the _accounts table + */ + apps(name receiver, name code, eosio::datastream ds); + + /** + * Manually sets the details of an app (admin only) + * + * @param account_name - name of the account + * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) + * @param username_hash - hash of the username + * @param origin - domain associated with the app + */ + [[eosio::action]] void adminsetapp( + name account_name, + string json_data, + checksum256 username_hash, + string origin); + + /** + * Removes an app (admin only) + * @param account_name - name of the account + */ + [[eosio::action]] void deleteapp(name account_name); + + /** + * Create a new account for an app and registers its details + * + * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) + * @param username_hash - Hash of the username + * @param origin - Domain associated with the app + * @param key - Public key generated from the account's password + */ + [[eosio::action]] void newapp( + string json_data, + checksum256 username_hash, + string origin, + public_key key); + + /** + * Adds a new key to a person's account to log into an app with + * + * @param account - account of the person + * @param app - account of the app to authorize the key to + * @param parent - parent permission of the new permission + * @param key - public key to authorize + */ + [[eosio::action]] void loginwithapp( + name account, + name app, + name parent, + public_key key); + + + /** + * Delete all the old apps + */ + [[eosio::action]] void eraseoldapps(); + + /** + * Buy RAM action allows an app to purchase RAM. + * It checks the account type of the app, ensures the RAM is being purchased with the correct token, + * and that the amount of tokens being used for the purchase is positive. + * It then calculates the amount of RAM to purchase based on the current RAM price, + * checks if there is enough available RAM, and allocates the purchased RAM to the app. + * Finally, it updates the total RAM used and available in the system, and + * transfers the tokens used for the purchase. + * + * @param dao_owner - the name of the DAO owner account + * @param app - the name of the app account purchasing the RAM + * @param quant - the amount and symbol of the tokens used for the purchase + */ + [[eosio::action]] void buyram(const name &dao_owner, const name &app, const asset &quant); + + /** + * Sell RAM action allows an app to sell RAM. + * It checks the account type of the app, ensures the RAM is being sold for the correct token, + * and that the amount of RAM being sold is positive. + * It then calculates the amount of tokens to return based on the current RAM price, + * checks if there is enough RAM being used by the app, and deallocates the sold RAM from the app. + * Finally, it updates the total RAM used in the system, and + * transfers the tokens from the sale. + * + * @param dao_owner - the name of the DAO owner account + * @param app - the name of the app account selling the RAM + * @param quant - the amount and symbol of the tokens used to sell + */ + [[eosio::action]] void sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant); + + struct [[eosio::table]] app + { + name account_name; + string app_name; + checksum256 username_hash; + string description; + string logo_url; + string origin; + + uint64_t primary_key() const { return account_name.value; } + checksum256 index_by_username_hash() const { return username_hash; } + checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } + }; + + typedef eosio::multi_index<"apps"_n, app, + eosio::indexed_by<"usernamehash"_n, + eosio::const_mem_fun>, + eosio::indexed_by<"originhash"_n, + eosio::const_mem_fun>> + apps_table; + + apps_table _apps; + + struct [[eosio::table]] appv2 + { + name account_name; + string json_data; // JSON string containing app details (name, description, logo URL, background_color, accent_color) + uint16_t version; // Version number to track schema changes + checksum256 username_hash; + string origin; + + uint64_t primary_key() const { return account_name.value; } + checksum256 index_by_username_hash() const { return username_hash; } + checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } + }; + + // Create a multi-index-table with two indexes + typedef eosio::multi_index<"appsv2"_n, appv2, + eosio::indexed_by<"usernamehash"_n, + eosio::const_mem_fun>, + eosio::indexed_by<"originhash"_n, + eosio::const_mem_fun>> + appsv2_table; + + // Create an instance of the table that can is initalized in the constructor + appsv2_table _appsv2; + + /** + * Returns the account name of the app that corresponds to the origin + * + * @param {string} origin - the origin of the app + * @example "https://www.tonomy.com" + * @param {name} [contract_name] - the name of the contract to query + * @returns {name} - the account name of the app + */ + static const name get_app_permission_by_origin(string origin, name contract_name = "id.tmy"_n) + { + apps_table id_apps = apps_table(contract_name, contract_name.value); + auto apps_by_origin_hash_itr = id_apps.get_index<"originhash"_n>(); + + eosio::checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); + check(origin_itr == apps_by_origin_hash_itr.end(), "No app with this origin found"); + + return origin_itr->account_name; + } + + /** + * Returns the account name of the app that corresponds to the origin + * + * @param {string} username - the username of the app + * @example "demo.app.tonomy.id" + * @param {name} [contract_name] - the name of the contract to query + * @returns {name} - the account name of the app + */ + static const name get_app_permission_by_username(string username, name contract_name = "tonomy"_n) + { + apps_table id_apps = apps_table(contract_name, contract_name.value); + auto apps_by_username_hash_itr = id_apps.get_index<"usernamehash"_n>(); + + eosio::checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + const auto username_itr = apps_by_username_hash_itr.find(username_hash); + check(username_itr == apps_by_username_hash_itr.end(), "No app with this username found"); + + return username_itr->account_name; + } + + using newapp_action = action_wrapper<"newapp"_n, &apps::newapp>; + using loginwithapp_action = action_wrapper<"loginwithapp"_n, &apps::loginwithapp>; + using adminsetapp_action = action_wrapper<"adminsetapp"_n, &apps::adminsetapp>; + using eraseoldapps_action = action_wrapper<"eraseoldapps"_n, &apps::eraseoldapps>; + using buyram_action = action_wrapper<"buyram"_n, &apps::buyram>; + using sellram_action = action_wrapper<"sellram"_n, &apps::sellram>; + + private: + /** + * Check if the app username is already taken + * + * @param username_hash - hash of the username of the account + */ + void check_app_username(const checksum256 &username_hash); + + /** + * Check if the app origin is already taken + * + * @param origin - domain associated with the app + */ + void check_app_origin(const string &origin); + }; +} \ No newline at end of file diff --git a/contracts/tonomy/include/tonomy/tonomy.hpp b/contracts/tonomy/include/tonomy/tonomy.hpp index f6f4ecc..6a07810 100644 --- a/contracts/tonomy/include/tonomy/tonomy.hpp +++ b/contracts/tonomy/include/tonomy/tonomy.hpp @@ -58,6 +58,15 @@ namespace tonomysystem }; typedef uint8_t permission_level_name; + // Shared helpers implemented in tonomy.cpp + void throwError(string error_code, string message); + uint64_t uint64_t_from_checksum256(const checksum256 &hash); + name tidy_name(const name &account_name, const uint8_t random_number, const enum_account_type &account_type); + name random_account_name(const checksum256 &hash1, const checksum256 &hash2, const enum_account_type &account_type); + authority create_authority_with_key(const eosio::public_key &key); + authority create_authority_with_account(const eosio::name &account); + permission_level create_eosio_code_permission_level(const name &account); + /** * The `eosio.tonomy` is the first sample of system contract provided by `block.one` through the EOSIO platform. It is a minimalist system contract because it only supplies the actions that are absolutely critical to bootstrap a chain and nothing more. This allows for a chain agnostic approach to bootstrapping a chain. * @@ -72,7 +81,6 @@ namespace tonomysystem static constexpr eosio::symbol system_resource_currency = eosio::symbol("TONO", 6); static constexpr eosio::name token_contract_name = "eosio.token"_n; - static constexpr eosio::name app_controller_account = "gov.tmy"_n; /** * Constructor for the contract, which initializes the _accounts table @@ -92,54 +100,7 @@ namespace tonomysystem checksum256 username_hash, public_key password_key, checksum256 password_salt); - /** - * Manually sets the details of an app (admin only) - * - * @param account_name - name of the account - * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) - * @param username_hash - hash of the username - * @param origin - domain associated with the app - */ - [[eosio::action]] void adminsetapp( - name account_name, - string json_data, - checksum256 username_hash, - string origin); - /** - * Removes an app (admin only) - * @param account_name - name of the account - */ - [[eosio::action]] void deleteapp(name account_name); - - /** - * Create a new account for an app and registers its details - * - * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) - * @param username_hash - Hash of the username - * @param origin - Domain associated with the app - * @param key - Public key generated from the account's password - */ - [[eosio::action]] void newapp( - string json_data, - checksum256 username_hash, - string origin, - public_key key); - - /** - * Adds a new key to a person's account to log into an app with - * - * @param account - account of the person - * @param app - account of the app to authorize the key to - * @param parent - parent permission of the new permission - * @param key - public key to authorize - */ - [[eosio::action]] void loginwithapp( - name account, - name app, - name parent, - public_key key); - /** * Update a key of a person * @@ -172,41 +133,6 @@ namespace tonomysystem */ [[eosio::action]] void setresparams(double ram_price, uint64_t total_ram_available, double ram_fee); - /** - * Delete all the old apps - */ - [[eosio::action]] void eraseoldapps(); - - /** - * Buy RAM action allows an app to purchase RAM. - * It checks the account type of the app, ensures the RAM is being purchased with the correct token, - * and that the amount of tokens being used for the purchase is positive. - * It then calculates the amount of RAM to purchase based on the current RAM price, - * checks if there is enough available RAM, and allocates the purchased RAM to the app. - * Finally, it updates the total RAM used and available in the system, and - * transfers the tokens used for the purchase. - * - * @param dao_owner - the name of the DAO owner account - * @param app - the name of the app account purchasing the RAM - * @param quant - the amount and symbol of the tokens used for the purchase - */ - [[eosio::action]] void buyram(const name &dao_owner, const name &app, const asset &quant); - - /** - * Sell RAM action allows an app to sell RAM. - * It checks the account type of the app, ensures the RAM is being sold for the correct token, - * and that the amount of RAM being sold is positive. - * It then calculates the amount of tokens to return based on the current RAM price, - * checks if there is enough RAM being used by the app, and deallocates the sold RAM from the app. - * Finally, it updates the total RAM used in the system, and - * transfers the tokens from the sale. - * - * @param dao_owner - the name of the DAO owner account - * @param app - the name of the app account selling the RAM - * @param quant - the amount and symbol of the tokens used to sell - */ - [[eosio::action]] void sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant); - struct [[eosio::table]] account_type_struct { name account_name; @@ -240,57 +166,6 @@ namespace tonomysystem // Create an instance of the table that can is initalized in the constructor people_table _people; - struct [[eosio::table]] app - { - name account_name; - string app_name; - checksum256 username_hash; - string description; - string logo_url; - string origin; - - // primary key automatically added by EOSIO method - uint64_t primary_key() const { return account_name.value; } - // also index by username hash - checksum256 index_by_username_hash() const { return username_hash; } - checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } - }; - - // Create a multi-index-table with two indexes - typedef eosio::multi_index<"apps"_n, app, - eosio::indexed_by<"usernamehash"_n, - eosio::const_mem_fun>, - eosio::indexed_by<"originhash"_n, - eosio::const_mem_fun>> - apps_table; - - // Create an instance of the table that can is initalized in the constructor - apps_table _apps; - - struct [[eosio::table]] appv2 - { - name account_name; - string json_data; // JSON string containing app details (name, description, logo URL, background_color, accent_color) - uint16_t version; // Version number to track schema changes - checksum256 username_hash; - string origin; - - uint64_t primary_key() const { return account_name.value; } - checksum256 index_by_username_hash() const { return username_hash; } - checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } - }; - - // Create a multi-index-table with two indexes - typedef eosio::multi_index<"appsv2"_n, appv2, - eosio::indexed_by<"usernamehash"_n, - eosio::const_mem_fun>, - eosio::indexed_by<"originhash"_n, - eosio::const_mem_fun>> - appsv2_table; - - // Create an instance of the table that can is initalized in the constructor - appsv2_table _appsv2; - struct [[eosio::table]] resource_config { double ram_fee; // RAM fee fraction (0.01 = 1% fee) @@ -306,69 +181,8 @@ namespace tonomysystem // Following line needed to correctly generate ABI. See https://github.com/EOSIO/eosio.cdt/issues/280#issuecomment-439666574 typedef eosio::multi_index<"resconfig"_n, resource_config> resource_config_table_dump; - /** - * Returns the account name of the app that corresponds to the origin - * - * @param {string} origin - the origin of the app - * @example "https://www.tonomy.com" - * @param {name} [contract_name] - the name of the contract to query - * @returns {name} - the account name of the app - */ - static const name get_app_permission_by_origin(string origin, name contract_name = "id.tmy"_n) - { - apps_table id_apps = apps_table(contract_name, contract_name.value); - auto apps_by_origin_hash_itr = id_apps.get_index<"originhash"_n>(); - - eosio::checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); - check(origin_itr == apps_by_origin_hash_itr.end(), "No app with this origin found"); - - return origin_itr->account_name; - } - - /** - * Returns the account name of the app that corresponds to the origin - * - * @param {string} username - the username of the app - * @example "demo.app.tonomy.id" - * @param {name} [contract_name] - the name of the contract to query - * @returns {name} - the account name of the app - */ - static const name get_app_permission_by_username(string username, name contract_name = "tonomy"_n) - { - apps_table id_apps = apps_table(contract_name, contract_name.value); - auto apps_by_username_hash_itr = id_apps.get_index<"usernamehash"_n>(); - - eosio::checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - const auto username_itr = apps_by_username_hash_itr.find(username_hash); - check(username_itr == apps_by_username_hash_itr.end(), "No app with this username found"); - - return username_itr->account_name; - } - using newperson_action = action_wrapper<"newperson"_n, &tonomy::newperson>; using updatekeyper_action = action_wrapper<"updatekeyper"_n, &tonomy::updatekeyper>; - using newapp_action = action_wrapper<"newapp"_n, &tonomy::newapp>; - using loginwithapp_action = action_wrapper<"loginwithapp"_n, &tonomy::loginwithapp>; - using adminsetapp_action = action_wrapper<"adminsetapp"_n, &tonomy::adminsetapp>; using setresparams_action = action_wrapper<"setresparams"_n, &tonomy::setresparams>; - using eraseoldapps_action = action_wrapper<"eraseoldapps"_n, &tonomy::eraseoldapps>; - using buyram_action = action_wrapper<"buyram"_n, &tonomy::buyram>; - using sellram_action = action_wrapper<"sellram"_n, &tonomy::sellram>; - - private: - /** - * Check if the app username is already taken - * - * @param username_hash - hash of the username of the account - */ - void check_app_username(const checksum256 &username_hash); - - /** - * Check if the app origin is already taken - * - * @param origin - domain associated with the app - */ - void check_app_origin(const string &origin); }; } \ No newline at end of file diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp new file mode 100644 index 0000000..7c677bd --- /dev/null +++ b/contracts/tonomy/src/apps.cpp @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tonomysystem { + +apps::apps(name receiver, name code, eosio::datastream ds) + : native(receiver, code, ds), + _apps(receiver, receiver.value), + _appsv2(receiver, receiver.value) {} + +void apps::newapp(string json_data, + checksum256 username_hash, + string origin, + public_key key) +{ + eosio::require_auth(get_self()); + + auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); + const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); + + authority owner_authority = create_authority_with_account(app_controller_account); + authority active_authority = create_authority_with_key(key); + active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); + + newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); + newaccountaction.send(get_self(), random_name, owner_authority, active_authority); + + auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); + const auto username_itr = apps_by_username_hash_itr.find(username_hash); + if (username_itr != apps_by_username_hash_itr.end()) { + throwError("TCON1001", "This app username is already taken"); + } + + auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); + const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); + if (origin_itr != apps_by_origin_hash_itr.end()) { + throwError("TCON1002", "This app origin is already taken"); + } + + tonomy::resource_config_table _resource_config(get_self(), get_self().value); + auto config = _resource_config.get(); + config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; + config.total_net_weight_allocated = this->initial_net_weight_allocation; + _resource_config.set(config, get_self()); + + _appsv2.emplace(get_self(), [&](auto &app_itr) { + app_itr.account_name = random_name; + app_itr.json_data = json_data; + app_itr.version = 2; + app_itr.username_hash = username_hash; + app_itr.origin = origin; + }); + + tonomy::account_type_table account_type(get_self(), get_self().value); + account_type.emplace(get_self(), [&](auto &row) { + row.account_name = random_name; + row.acc_type = enum_account_type::App; + row.version = 1; + }); +} + +void apps::eraseoldapps() +{ + eosio::require_auth(get_self()); + while (_apps.begin() != _apps.end()) { + _apps.erase(_apps.begin()); + } +} + +void apps::check_app_username(const checksum256 &username_hash) +{ + auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); + const auto username_itr = apps_by_username_hash_itr.find(username_hash); + if (username_itr != apps_by_username_hash_itr.end()) { + throwError("TCON1001", "This app username is already taken"); + } +} + +void apps::check_app_origin(const string &origin) +{ + auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); + const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); + if (origin_itr != apps_by_origin_hash_itr.end()) { + throwError("TCON1002", "This app origin is already taken"); + } +} + +void apps::adminsetapp(name account_name, + string json_data, + checksum256 username_hash, + string origin) +{ + eosio::require_auth(get_self()); + eosio::check(is_account(account_name), "Account does not exist"); + + tonomy::account_type_table account_type(get_self(), get_self().value); + auto itr = account_type.find(account_name.value); + if (itr == account_type.end()) { + account_type.emplace(get_self(), [&](auto &row) { + row.account_name = account_name; + row.acc_type = enum_account_type::App; + row.version = 1; + }); + } + + auto apps_itr = _appsv2.find(account_name.value); + + if (apps_itr != _appsv2.end()) { + if (apps_itr->origin != origin) { + check_app_origin(origin); + } + if (apps_itr->username_hash != username_hash) { + check_app_username(username_hash); + } + _appsv2.modify(apps_itr, get_self(), [&](auto &app_itr) { + app_itr.account_name = account_name; + app_itr.origin = origin; + app_itr.username_hash = username_hash; + app_itr.json_data = json_data; + app_itr.version = 2; + }); + } else { + check_app_username(username_hash); + check_app_origin(origin); + _appsv2.emplace(get_self(), [&](auto &app_itr) { + app_itr.account_name = account_name; + app_itr.origin = origin; + app_itr.username_hash = username_hash; + app_itr.json_data = json_data; + app_itr.version = 2; + }); + } +} + +void apps::deleteapp(name account_name) +{ + eosio::require_auth(get_self()); + + auto itr1 = _apps.find(account_name.value); + if (itr1 != _apps.end()) { + _apps.erase(itr1); + } + + auto itr2 = _appsv2.find(account_name.value); + if (itr2 != _appsv2.end()) { + _appsv2.erase(itr2); + } +} + +void apps::loginwithapp(name account, + name app, + name parent, + public_key key) +{ + auto app_itr = _appsv2.find(app.value); + check(app_itr != _appsv2.end(), "App does not exist"); + + authority authority = create_authority_with_key(key); + + eosiotonomy::bios::updateauth_action updateauthaction("eosio"_n, {account, parent}); + updateauthaction.send(account, app, parent, authority); +} + +void apps::buyram(const name &dao_owner, const name &app, const asset &quant) +{ + require_auth(app); + + tonomy::account_type_table account_type(get_self(), get_self().value); + auto itr = account_type.find(app.value); + eosio::check(itr != account_type.end(), "Could not find account"); + eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); + + eosio::check(quant.symbol == tonomy::system_resource_currency, "must buy ram with core token"); + eosio::check(quant.amount > 0, "Amount must be positive"); + + tonomy::resource_config_table config_table(get_self(), get_self().value); + tonomy::resource_config config; + if (config_table.exists()) { + config = config_table.get(); + } else { + eosio::check(false, "Resource config does not exist"); + } + + eosio::check(config.ram_price != 0, "Failed to retrieve ram_price from resource config"); + eosio::check(config.ram_fee != 0, "Failed to retrieve ram_fee from resource config"); + + double ram_price = config.ram_price; + double ram_fee = (1.0 + config.ram_fee); + double amount = static_cast(quant.amount) / pow(10, quant.symbol.precision()); + uint64_t ram_purchase = amount * ram_price / ram_fee; + eosio::check(config.total_ram_available >= config.total_ram_used + ram_purchase, "Not enough RAM available"); + + config.total_ram_used += ram_purchase; + config_table.set(config, get_self()); + + int64_t myRAM, myNET, myCPU; + eosio::get_resource_limits(app, myRAM, myNET, myCPU); + eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"buyram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM + ram_purchase, "}]"); + eosio::set_resource_limits(app, myRAM + ram_purchase, myNET, myNET); + + eosio::action(permission_level{dao_owner, "active"_n}, + tonomy::token_contract_name, + "transfer"_n, + std::make_tuple(dao_owner, native::governance_name, quant, std::string("buy ram"))) + .send(); +} + +void apps::sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant) +{ + require_auth(app); + + tonomy::account_type_table account_type(get_self(), get_self().value); + auto itr = account_type.find(app.value); + eosio::check(itr != account_type.end(), "Could not find account"); + eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); + + eosio::check(quant.symbol == tonomy::system_resource_currency, "must sell ram with core token"); + eosio::check(quant.amount > 0, "Amount must be positive"); + + tonomy::resource_config_table resource_config_singleton(get_self(), get_self().value); + auto config = resource_config_singleton.get(); + + double ram_price = config.ram_price; + double ram_fee = (1.0 + config.ram_fee); + double amount = static_cast(quant.amount) / pow(10, quant.symbol.precision()); + uint64_t ram_sold = ram_price * ram_fee * amount; + + config.total_ram_used -= ram_sold; + eosio::check(config.total_ram_used >= 0, "Cannot have less than 0 RAM used"); + resource_config_singleton.set(config, get_self()); + + int64_t myRAM, myNET, myCPU; + eosio::get_resource_limits(app, myRAM, myNET, myCPU); + eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"sellram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM - ram_sold, "}]"); + eosio::check(myRAM - ram_sold >= 0, "Account cannot have less than 0 RAM"); + eosio::set_resource_limits(app, myRAM - ram_sold, myNET, myNET); + + eosio::action(permission_level{get_self(), "active"_n}, + tonomy::token_contract_name, + "transfer"_n, + std::make_tuple(native::governance_name, dao_owner, eosio::asset(ram_sold, tonomy::system_resource_currency), std::string("sell ram"))) + .send(); +} + +} // namespace tonomysystem diff --git a/contracts/tonomy/src/tonomy.cpp b/contracts/tonomy/src/tonomy.cpp index 5db9cf4..3c07da7 100644 --- a/contracts/tonomy/src/tonomy.cpp +++ b/contracts/tonomy/src/tonomy.cpp @@ -10,9 +10,7 @@ namespace tonomysystem // contract class constructor tonomy::tonomy(name receiver, name code, eosio::datastream ds) : native(receiver, code, ds), // instantiate multi-index instance as data member (find it defined below) - _people(receiver, receiver.value), - _apps(receiver, receiver.value), - _appsv2(receiver, receiver.value) + _people(receiver, receiver.value) { } @@ -154,171 +152,6 @@ namespace tonomysystem row.version = 1; }); } - void tonomy::newapp(string json_data, - checksum256 username_hash, - string origin, - public_key key) - { - // TODO: in the future only an organization type can create an app - // check the transaction is signed by the `id.tmy` account - eosio::require_auth(get_self()); - - // generate new random account name - auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); - const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); - - // use the password_key public key for the owner authority - authority owner_authority = create_authority_with_account(app_controller_account); - authority active_authority = create_authority_with_key(key); - active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); - - // If the account name exists, this will fail - newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); - newaccountaction.send(get_self(), random_name, owner_authority, active_authority); - - // Check the username is not already taken - auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); - const auto username_itr = apps_by_username_hash_itr.find(username_hash); - if (username_itr != apps_by_username_hash_itr.end()) - { - throwError("TCON1001", "This app username is already taken"); - } - - // Check the origin is not already taken - auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); - const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); - if (origin_itr != apps_by_origin_hash_itr.end()) - { - throwError("TCON1002", "This app origin is already taken"); - } - - tonomy::resource_config_table _resource_config(get_self(), get_self().value); - auto config = _resource_config.get(); - config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; - config.total_net_weight_allocated = this->initial_net_weight_allocation; - _resource_config.set(config, get_self()); - - // Store the password_salt and hashed username in table - // Store the app details in JSON format - _appsv2.emplace(get_self(), [&](auto &app_itr) - { - app_itr.account_name = random_name; - app_itr.json_data = json_data; - app_itr.version = 2; - app_itr.username_hash = username_hash; - app_itr.origin = origin; - }); - - // Store the account type in the account_type table - account_type_table account_type(get_self(), get_self().value); - account_type.emplace(get_self(), [&](auto &row) - { - row.account_name = random_name; - row.acc_type = enum_account_type::App; - row.version = 1; }); - } - - void tonomy::eraseoldapps() { - eosio::require_auth(get_self()); - - // Delete all items in the v1 table - while (_apps.begin() != _apps.end()) { - _apps.erase(_apps.begin()); - } - } - - void tonomy::check_app_username(const checksum256 &username_hash) - { - // Check the username is not already taken - auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); - const auto username_itr = apps_by_username_hash_itr.find(username_hash); - if (username_itr != apps_by_username_hash_itr.end()) - { - throwError("TCON1001", "This app username is already taken"); - } - } - void tonomy::check_app_origin(const string &origin) { - // Check the origin is not already taken - auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); - const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); - if (origin_itr != apps_by_origin_hash_itr.end()) - { - throwError("TCON1002", "This app origin is already taken"); - } - } - - void tonomy::adminsetapp( - name account_name, - string json_data, - checksum256 username_hash, - string origin) - { - eosio::require_auth(get_self()); // signed by tonomy@active permission - - eosio::check(is_account(account_name), "Account does not exist"); - - // Add to the account_type table - account_type_table account_type(get_self(), get_self().value); - - auto itr = account_type.find(account_name.value); - if (itr == account_type.end()) - { - account_type.emplace(get_self(), [&](auto &row) { - row.account_name = account_name; - row.acc_type = enum_account_type::App; - row.version = 1; - }); - } - - // Check the account name is not already used - auto apps_itr = _appsv2.find(account_name.value); - - if (apps_itr != _appsv2.end()) - { - if (apps_itr->origin != origin) { - check_app_origin(origin); - } - if (apps_itr->username_hash != username_hash) { - check_app_username(username_hash); - } - _appsv2.modify(apps_itr, get_self(), [&](auto &app_itr) { - app_itr.account_name = account_name; - app_itr.origin = origin; - app_itr.username_hash = username_hash; - app_itr.json_data = json_data; - app_itr.version = 2; - }); - } else { - check_app_username(username_hash); - check_app_origin(origin); - _appsv2.emplace(get_self(), [&](auto &app_itr) { - app_itr.account_name = account_name; - app_itr.origin = origin; - app_itr.username_hash = username_hash; - app_itr.json_data = json_data; - app_itr.version = 2; - }); - } - } - - void tonomy::deleteapp(name account_name) { - eosio::require_auth(get_self()); // signed by tonomy@active permission - - auto itr1 = _apps.find(account_name.value); - if (itr1 != _apps.end()) - { - _apps.erase(itr1); - } - - auto itr2 = _appsv2.find(account_name.value); - if (itr2 != _appsv2.end()) - { - _appsv2.erase(itr2); - } - } - void tonomy::updatekeyper(name account, permission_level_name permission_level, public_key key, @@ -385,33 +218,6 @@ namespace tonomysystem updateauthaction.send(account, "active"_n, "owner"_n, active); } - void tonomy::loginwithapp( - name account, - name app, - name parent, - public_key key) - { - // eosio::require_auth(account); // this is not needed as tonomy::tonomy::updateauth_action checks the permission - - // check the app exists and is registered with status - auto app_itr = _appsv2.find(app.value); - check(app_itr != _appsv2.end(), "App does not exist"); - - // TODO: uncomment when apps have status - // check(app_itr->status == tonomy::enum_account_status::Active_Status, "App is not active"); - - // TODO: check parent is only from allowed parents : "local", "pin", "biometric", "active" - - // TODO: instead of "app" as the permission, use sha256(parent, app, name of key(TODO: provide as argument with default = "main")) - - // setup the new key authoritie(s) - authority authority = create_authority_with_key(key); - - eosiotonomy::bios::updateauth_action updateauthaction("eosio"_n, {account, parent}); - updateauthaction.send(account, app, parent, authority); - // must be signed by the account's permission_level or parent (from eosio.tonomy::updateauth()) - } - void tonomy::setresparams(double ram_price, uint64_t total_ram_available, double ram_fee) { require_auth(native::governance_name); // check authorization is gov.tmy @@ -443,113 +249,4 @@ namespace tonomysystem resource_config_singleton.set(config, get_self()); } - void tonomy::buyram(const name &dao_owner, const name &app, const asset &quant) - { - require_auth(app); // Check that the app has the necessary authorization - - // Access the account table from id.tmy.hpp - tonomy::tonomy::account_type_table account_type(get_self(), get_self().value); - // Check the account type of the app - auto itr = account_type.find(app.value); - eosio::check(itr != account_type.end(), "Could not find account"); - eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); - - // Check that the RAM is being purchased with the correct token - eosio::check(quant.symbol == tonomy::system_resource_currency, "must buy ram with core token"); - - // Check that the amount of tokens being used for the purchase is positive - eosio::check(quant.amount > 0, "Amount must be positive"); - - // Get the RAM price - // resource_config_table resource_config_singleton(get_self(), get_self().value); - // auto config = resource_config_singleton.get(); - resource_config_table config_table(get_self(), get_self().value); - - // Retrieve the resource_config object from the table - resource_config config; - if (config_table.exists()) - { - config = config_table.get(); - } - else - { - eosio::check(false, "Resource config does not exist"); - } - - // Check if the values are retrieved successfully - eosio::check(config.ram_price != 0, "Failed to retrieve ram_price from resource config"); - eosio::check(config.ram_fee != 0, "Failed to retrieve ram_fee from resource config"); - - // Read values from the table - double ram_price = config.ram_price; - double ram_fee = (1.0 + config.ram_fee); - double amount = static_cast(quant.amount) / pow(10, quant.symbol.precision()); - uint64_t ram_purchase = amount * ram_price / ram_fee; - eosio::check(config.total_ram_available >= config.total_ram_used + ram_purchase, "Not enough RAM available"); - - // modify the values and save them back to the table, - config.total_ram_used += ram_purchase; - config_table.set(config, get_self()); - - // Allocate the RAM - int64_t myRAM, myNET, myCPU; - eosio::get_resource_limits(app, myRAM, myNET, myCPU); - eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"buyram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM + ram_purchase, "}]"); - eosio::set_resource_limits(app, myRAM + ram_purchase, myNET, myNET); - - eosio::action(permission_level{dao_owner, "active"_n}, - token_contract_name, - "transfer"_n, - std::make_tuple(dao_owner, native::governance_name, quant, std::string("buy ram"))) - .send(); - } - - void tonomy::sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant) - { - require_auth(app); // Check that the app has the necessary authorization - - // Access the account table from id.tmy.hpp - tonomy::tonomy::account_type_table account_type(get_self(), get_self().value); - // Check the account type of the app - auto itr = account_type.find(app.value); - eosio::check(itr != account_type.end(), "Could not find account"); - eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); - - // Check that the RAM is being purchased with the correct token - eosio::check(quant.symbol == tonomy::system_resource_currency, "must sell ram with core token"); - - // Check that the amount of bytes being sold is positive - eosio::check(quant.amount > 0, "Amount must be positive"); - - // Get the RAM price - resource_config_table resource_config_singleton(get_self(), get_self().value); - auto config = resource_config_singleton.get(); - - // Read values from the table - double ram_price = config.ram_price; - double ram_fee = (1.0 + config.ram_fee); - double amount = static_cast(quant.amount) / pow(10, quant.symbol.precision()); - uint64_t ram_sold = ram_price * ram_fee * amount; - - // Modify the values and save them back to the table - config.total_ram_used -= ram_sold; - eosio::check(config.total_ram_used >= 0, "Cannot have less than 0 RAM used"); - resource_config_singleton.set(config, get_self()); - - // Deallocate the RAM - int64_t myRAM, myNET, myCPU; - eosio::get_resource_limits(app, myRAM, myNET, myCPU); - eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"sellram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM - ram_sold, "}]"); - eosio::check(myRAM - ram_sold >= 0, "Account cannot have less than 0 RAM"); - eosio::set_resource_limits(app, myRAM - ram_sold, myNET, myNET); - - // Transfer token and sell RAM - // TODO: should buy and sell from proxy counttract, otherwise cannot autorize to sell ram - eosio::action(permission_level{get_self(), "active"_n}, - token_contract_name, - "transfer"_n, - std::make_tuple(native::governance_name, dao_owner, eosio::asset(ram_sold, tonomy::system_resource_currency), std::string("sell ram"))) - .send(); - } - } From a7635fa036e91dede67d627ab4525390c9137420 Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 14:58:47 +0100 Subject: [PATCH 2/8] feat: new data structure for apps and smart contract --- contracts/tonomy/include/tonomy/apps.hpp | 81 +++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index c35c377..2642128 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -155,7 +155,14 @@ namespace tonomysystem struct [[eosio::table]] appv2 { name account_name; - string json_data; // JSON string containing app details (name, description, logo URL, background_color, accent_color) + string json_data; // JSON string containing app details + // { + // app_name: string; + // description: string; + // logo_url: string; + // background_color: string; // hex string starting with # + // accent_color: string; // hex string starting with # + // }; uint16_t version; // Version number to track schema changes checksum256 username_hash; string origin; @@ -176,6 +183,64 @@ namespace tonomysystem // Create an instance of the table that can is initalized in the constructor appsv2_table _appsv2; + // ---------------------------------------------------------------------- + // Apps V3 + // ---------------------------------------------------------------------- + // Plan enum for subscription tiers + enum plan_t : uint8_t { + plan_basic = 0, + plan_pro = 1 + }; + + struct [[eosio::table]] appv3 + { + name account_name; // app account name + string json_data; // JSON string containing app details: + // { + // app_name: string; + // description: string; + // logo_url: string; + // background_color: string; // hex string starting with # + // accent_color: string; // hex string starting with # + // }; + uint16_t version; // schema/data version + string username; // raw username string (e.g., "coolapp") + string origin; // app domain + uint8_t plan; // subscription plan (0 = basic, 1 = pro) + + uint64_t primary_key() const { return account_name.value; } + checksum256 index_by_username_hash() const { return eosio::sha256(username.c_str(), std::strlen(username.c_str())); } + checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } + }; + + // Multi-index for appsv3 with indexes on username (via hash) and origin + typedef eosio::multi_index<"appsv3"_n, appv3, + eosio::indexed_by<"usernamehash"_n, + eosio::const_mem_fun>, + eosio::indexed_by<"originhash"_n, + eosio::const_mem_fun> + > appsv3_table; + + // Instance to be initialized in the constructor + appsv3_table _appsv3; + + // ---------------------------------------------------------------------- + // Smart Contract info per app + // ---------------------------------------------------------------------- + struct [[eosio::table]] smartcontract + { + name account_name; // app account + uint16_t version; // deployment/version number + uint32_t ram_purchased_mb; // purchased RAM in MB + string source_code_url; // optional: empty string if not set + + uint64_t primary_key() const { return account_name.value; } + }; + + typedef eosio::multi_index<"appscntrct"_n, smartcontract> smartcontract_table; + + smartcontract_table _smartcontracts; + /** * Returns the account name of the app that corresponds to the origin * @@ -237,5 +302,19 @@ namespace tonomysystem * @param origin - domain associated with the app */ void check_app_origin(const string &origin); + + /** + * Check if the raw username is already taken in appsv3 + * + * @param username - raw username string (may include leading '@') + */ + void check_app_username_v3(const string &username) + { + // Compute hash index from raw username for efficient lookup + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + auto idx = _appsv3.get_index<"usernamehash"_n>(); + auto itr = idx.find(username_hash); + check(itr == idx.end(), "Username already taken"); + } }; } \ No newline at end of file From a9c7c3e13e26a06b6cd9ebb97ba065048c0fd24b Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 15:31:27 +0100 Subject: [PATCH 3/8] feat: added new action definitions --- contracts/tonomy/include/tonomy/apps.hpp | 310 ++++++++++++++--------- contracts/tonomy/src/apps.cpp | 29 ++- 2 files changed, 218 insertions(+), 121 deletions(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index 2642128..dfae3a1 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "native.hpp" namespace tonomysystem @@ -45,39 +46,41 @@ namespace tonomysystem */ apps(name receiver, name code, eosio::datastream ds); - /** - * Manually sets the details of an app (admin only) - * - * @param account_name - name of the account - * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) - * @param username_hash - hash of the username - * @param origin - domain associated with the app - */ - [[eosio::action]] void adminsetapp( - name account_name, - string json_data, - checksum256 username_hash, - string origin); - - /** - * Removes an app (admin only) - * @param account_name - name of the account - */ - [[eosio::action]] void deleteapp(name account_name); - - /** - * Create a new account for an app and registers its details - * - * @param json_data - JSON string containing app details (name,description, logo_url, background_color, accent_color) - * @param username_hash - Hash of the username - * @param origin - Domain associated with the app - * @param key - Public key generated from the account's password - */ - [[eosio::action]] void newapp( - string json_data, - checksum256 username_hash, - string origin, - public_key key); + /** + * Create a new app account and register its details (sets plan = basic) + * + * @param account_name - the account that creates the app + * @param json_data - JSON with display details: app_name, description, logo_url, background_color, accent_color + * @param username - raw username string (e.g., "coolapp" or "@coolapp"); must be unique + * @param origin - domain or origin associated with the app; must be unique + */ + [[eosio::action]] void appcreate( + name account_name, + string json_data, + string username, + string origin); + + /** + * Update app data + * + * @param account_name - the app account name + * @param json_data - updated JSON data (full) + * @param username - new raw username; must be unique if changed + */ + [[eosio::action]] void appupdate( + name account_name, + string json_data, + string username); + + /** + * Update the app subscription plan + * + * @param account_name - the app account name + * @param plan - subscription plan enum: 0 = basic, 1 = pro + */ + [[eosio::action]] void appupdplan( + name account_name, + uint8_t plan); /** * Adds a new key to a person's account to log into an app with @@ -94,63 +97,81 @@ namespace tonomysystem public_key key); - /** - * Delete all the old apps - */ - [[eosio::action]] void eraseoldapps(); - - /** - * Buy RAM action allows an app to purchase RAM. - * It checks the account type of the app, ensures the RAM is being purchased with the correct token, - * and that the amount of tokens being used for the purchase is positive. - * It then calculates the amount of RAM to purchase based on the current RAM price, - * checks if there is enough available RAM, and allocates the purchased RAM to the app. - * Finally, it updates the total RAM used and available in the system, and - * transfers the tokens used for the purchase. - * - * @param dao_owner - the name of the DAO owner account - * @param app - the name of the app account purchasing the RAM - * @param quant - the amount and symbol of the tokens used for the purchase - */ - [[eosio::action]] void buyram(const name &dao_owner, const name &app, const asset &quant); - - /** - * Sell RAM action allows an app to sell RAM. - * It checks the account type of the app, ensures the RAM is being sold for the correct token, - * and that the amount of RAM being sold is positive. - * It then calculates the amount of tokens to return based on the current RAM price, - * checks if there is enough RAM being used by the app, and deallocates the sold RAM from the app. - * Finally, it updates the total RAM used in the system, and - * transfers the tokens from the sale. - * - * @param dao_owner - the name of the DAO owner account - * @param app - the name of the app account selling the RAM - * @param quant - the amount and symbol of the tokens used to sell - */ - [[eosio::action]] void sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant); - - struct [[eosio::table]] app - { - name account_name; - string app_name; - checksum256 username_hash; - string description; - string logo_url; - string origin; - - uint64_t primary_key() const { return account_name.value; } - checksum256 index_by_username_hash() const { return username_hash; } - checksum256 index_by_origin_hash() const { return eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); } - }; - - typedef eosio::multi_index<"apps"_n, app, - eosio::indexed_by<"usernamehash"_n, - eosio::const_mem_fun>, - eosio::indexed_by<"originhash"_n, - eosio::const_mem_fun>> - apps_table; - - apps_table _apps; + /** + * Deploy initial smart contract code, ABI, and metadata for an app (sets version = 1) + * + * @param account_name - the app account name + * @param vmtype - WebAssembly VM type (as in `setcode`) + * @param vmversion - WebAssembly VM version (as in `setcode`) + * @param code - WASM code bytes for the contract + * @param abi - ABI bytes for the contract + * @param source_code_url - optional URL to the contract source code (empty for none) + */ + [[eosio::action]] void scdeploy( + name account_name, + uint8_t vmtype, + uint8_t vmversion, + const std::vector &code, + const std::vector &abi, + string source_code_url); + + /** + * Update smart contract code, ABI, and/or metadata for an app (increments version) + * + * @param account_name - the app account name + * @param vmtype - WebAssembly VM type (as in `setcode`) + * @param vmversion - WebAssembly VM version (as in `setcode`) + * @param code - WASM code bytes for the contract (empty vector to skip) + * @param abi - ABI bytes for the contract (empty vector to skip) + * @param source_code_url - optional new source code URL (empty to leave unchanged) + */ + [[eosio::action]] void scupdate( + name account_name, + uint8_t vmtype, + uint8_t vmversion, + const std::vector &code, + const std::vector &abi, + string source_code_url); + + /** + * Buy RAM for the app's smart contract using core tokens + * + * @param account_name - the app account name + * @param quant - amount of core tokens to spend for RAM + */ + [[eosio::action]] void scbuyram( + const name &account_name, + const asset &quant); + + /** + * Sell RAM from the app's smart contract and return core tokens + * + * @param account_name - the app account name + * @param quant - amount of core tokens to sell for RAM reduction + */ + [[eosio::action]] void scsellram( + const name &account_name, + const asset &quant); + + /** + * Add a new key to the app account's active permission + * + * @param account_name - the app account name + * @param key - the new public key to add + */ + [[eosio::action]] void appaddkey( + name account_name, + public_key key); + + /** + * Remove a key from the app account's active permission + * + * @param account_name - the app account name + * @param key - the public key to remove + */ + [[eosio::action]] void appremkey( + name account_name, + public_key key); struct [[eosio::table]] appv2 { @@ -249,44 +270,95 @@ namespace tonomysystem * @param {name} [contract_name] - the name of the contract to query * @returns {name} - the account name of the app */ - static const name get_app_permission_by_origin(string origin, name contract_name = "id.tmy"_n) - { - apps_table id_apps = apps_table(contract_name, contract_name.value); - auto apps_by_origin_hash_itr = id_apps.get_index<"originhash"_n>(); - - eosio::checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); - check(origin_itr == apps_by_origin_hash_itr.end(), "No app with this origin found"); - - return origin_itr->account_name; - } + static const name get_app_permission_by_origin(string origin, name contract_name = "id.tmy"_n); /** - * Returns the account name of the app that corresponds to the origin + * Returns the account name of the app that corresponds to the username * * @param {string} username - the username of the app - * @example "demo.app.tonomy.id" + * @example "demo.app.tonomy.id" or "@coolapp" * @param {name} [contract_name] - the name of the contract to query * @returns {name} - the account name of the app */ - static const name get_app_permission_by_username(string username, name contract_name = "tonomy"_n) - { - apps_table id_apps = apps_table(contract_name, contract_name.value); - auto apps_by_username_hash_itr = id_apps.get_index<"usernamehash"_n>(); - - eosio::checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - const auto username_itr = apps_by_username_hash_itr.find(username_hash); - check(username_itr == apps_by_username_hash_itr.end(), "No app with this username found"); + static const name get_app_permission_by_username(string username, name contract_name = "tonomy"_n); - return username_itr->account_name; - } - - using newapp_action = action_wrapper<"newapp"_n, &apps::newapp>; using loginwithapp_action = action_wrapper<"loginwithapp"_n, &apps::loginwithapp>; - using adminsetapp_action = action_wrapper<"adminsetapp"_n, &apps::adminsetapp>; - using eraseoldapps_action = action_wrapper<"eraseoldapps"_n, &apps::eraseoldapps>; - using buyram_action = action_wrapper<"buyram"_n, &apps::buyram>; - using sellram_action = action_wrapper<"sellram"_n, &apps::sellram>; + + // Action wrappers + using appcreate_action = action_wrapper<"appcreate"_n, &apps::appcreate>; + using appupdate_action = action_wrapper<"appupdate"_n, &apps::appupdate>; + using appupdplan_action = action_wrapper<"appupdplan"_n, &apps::appupdplan>; + using scdeploy_action = action_wrapper<"scdeploy"_n, &apps::scdeploy>; + using scupdate_action = action_wrapper<"scupdate"_n, &apps::scupdate>; + using scbuyram_action = action_wrapper<"scbuyram"_n, &apps::scbuyram>; + using scsellram_action = action_wrapper<"scsellram"_n, &apps::scsellram>; + using appaddkey_action = action_wrapper<"appaddkey"_n, &apps::appaddkey>; + using appremkey_action = action_wrapper<"appremkey"_n, &apps::appremkey>; + + /** + * Admin: create or set an app record + * + * @param json_data - JSON with display details + * @param username - raw username (unique) + * @param origin - domain (unique) + */ + [[eosio::action]] void admncrtapp( + string json_data, + string username, + string origin); + + /** + * Admin: update an app record + * + * @param account_name - the app account name + * @param json_data - JSON with display details + * @param username - raw username (unique) + * @param origin - domain (unique) + * @param plan - subscription plan enum: 0 = basic, 1 = pro + */ + [[eosio::action]] void admnupdapp( + name account_name, + string json_data, + string username, + string origin, + uint8_t plan); + + /** + * Admin: delete an app record + * + * @param account_name - the app account name + */ + [[eosio::action]] void admndelapp( + name account_name); + + /** + * Admin: migrate a single app from V2 + * + * @param account_name - the app account name to migrate + * @param username - raw username string (e.g., "coolapp" or "@coolapp") + * @param plan - subscription plan enum: 0 = basic, 1 = pro (default to basic) + */ + [[eosio::action]] void admnmigapp( + name account_name, + string username, + uint8_t plan); + + /** + * Admin: migrate smart contract metadata for an app + * Note: RAM info is fetched from get_resource_limits for the account + * + * @param account_name - the app account name + * @param source_code_url - optional URL to the contract source code + */ + [[eosio::action]] void admnmigsc( + name account_name, + string source_code_url); + + using admncrtapp_action = action_wrapper<"admncrtapp"_n, &apps::admncrtapp>; + using admnupdapp_action = action_wrapper<"admnupdapp"_n, &apps::admnupdapp>; + using admndelapp_action = action_wrapper<"admndelapp"_n, &apps::admndelapp>; + using admnmigapp_action = action_wrapper<"admnmigapp"_n, &apps::admnmigapp>; + using admnmigsc_action = action_wrapper<"admnmigsc"_n, &apps::admnmigsc>; private: /** diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index 7c677bd..661523c 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -11,8 +11,9 @@ namespace tonomysystem { apps::apps(name receiver, name code, eosio::datastream ds) : native(receiver, code, ds), - _apps(receiver, receiver.value), - _appsv2(receiver, receiver.value) {} + _appsv2(receiver, receiver.value), + _appsv3(receiver, receiver.value), + _smartcontracts(receiver, receiver.value) {} void apps::newapp(string json_data, checksum256 username_hash, @@ -250,4 +251,28 @@ void apps::sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant) .send(); } +const name apps::get_app_permission_by_origin(string origin, name contract_name) +{ + appsv3_table appsv3(contract_name, contract_name.value); + auto apps_by_origin_hash_itr = appsv3.get_index<"originhash"_n>(); + + eosio::checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); + check(origin_itr != apps_by_origin_hash_itr.end(), "No app with this origin found"); + + return origin_itr->account_name; +} + +const name apps::get_app_permission_by_username(string username, name contract_name) +{ + appsv3_table appsv3(contract_name, contract_name.value); + auto apps_by_username_hash_itr = appsv3.get_index<"usernamehash"_n>(); + + eosio::checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + const auto username_itr = apps_by_username_hash_itr.find(username_hash); + check(username_itr != apps_by_username_hash_itr.end(), "No app with this username found"); + + return username_itr->account_name; +} + } // namespace tonomysystem From 5b53f0d7cb6b3e86a780e0ecb16101d4bcab325b Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 16:51:12 +0100 Subject: [PATCH 4/8] feat: updated the existing function implementations to the new interface --- contracts/tonomy/include/tonomy/apps.hpp | 11 +- contracts/tonomy/src/apps.cpp | 351 ++++++++++++++++------- 2 files changed, 246 insertions(+), 116 deletions(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index dfae3a1..fe12d66 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -47,15 +47,15 @@ namespace tonomysystem apps(name receiver, name code, eosio::datastream ds); /** - * Create a new app account and register its details (sets plan = basic) + * Create a new app account with random name and register its details (sets plan = basic) * - * @param account_name - the account that creates the app + * @param creator - the account name of the creator (used for auth and active permission) * @param json_data - JSON with display details: app_name, description, logo_url, background_color, accent_color * @param username - raw username string (e.g., "coolapp" or "@coolapp"); must be unique * @param origin - domain or origin associated with the app; must be unique */ [[eosio::action]] void appcreate( - name account_name, + name creator, string json_data, string username, string origin); @@ -252,7 +252,6 @@ namespace tonomysystem { name account_name; // app account uint16_t version; // deployment/version number - uint32_t ram_purchased_mb; // purchased RAM in MB string source_code_url; // optional: empty string if not set uint64_t primary_key() const { return account_name.value; } @@ -337,11 +336,13 @@ namespace tonomysystem * @param account_name - the app account name to migrate * @param username - raw username string (e.g., "coolapp" or "@coolapp") * @param plan - subscription plan enum: 0 = basic, 1 = pro (default to basic) + * @param key - public key to initialize/attach to the app during migration */ [[eosio::action]] void admnmigapp( name account_name, string username, - uint8_t plan); + uint8_t plan, + public_key key); /** * Admin: migrate smart contract metadata for an app diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index 661523c..ec508da 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -10,55 +10,63 @@ namespace tonomysystem { apps::apps(name receiver, name code, eosio::datastream ds) - : native(receiver, code, ds), - _appsv2(receiver, receiver.value), - _appsv3(receiver, receiver.value), - _smartcontracts(receiver, receiver.value) {} - -void apps::newapp(string json_data, - checksum256 username_hash, - string origin, - public_key key) + : native(receiver, code, ds), + _appsv2(receiver, receiver.value), + _appsv3(receiver, receiver.value), + _smartcontracts(receiver, receiver.value) {} + +// Admin create app with random account name +void apps::admncrtapp(string json_data, + string username, + string origin) { - eosio::require_auth(get_self()); + require_auth(get_self()); + // Uniqueness checks for username and origin + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + { + auto uidx = _appsv3.get_index<"usernamehash"_n>(); + check(uidx.find(username_hash) == uidx.end(), "Username already taken"); + } + checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + { + auto oidx = _appsv3.get_index<"originhash"_n>(); + check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); + } + + // Generate random account name from username and json_data hashes auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); + // Create account with owner=gov.tmy, active=contract authority owner_authority = create_authority_with_account(app_controller_account); - authority active_authority = create_authority_with_key(key); + authority active_authority = create_authority_with_account(get_self()); active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); newaccountaction.send(get_self(), random_name, owner_authority, active_authority); - auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); - const auto username_itr = apps_by_username_hash_itr.find(username_hash); - if (username_itr != apps_by_username_hash_itr.end()) { - throwError("TCON1001", "This app username is already taken"); - } - - auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); - const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); - if (origin_itr != apps_by_origin_hash_itr.end()) { - throwError("TCON1002", "This app origin is already taken"); - } - + // Update resource config tonomy::resource_config_table _resource_config(get_self(), get_self().value); auto config = _resource_config.get(); config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; config.total_net_weight_allocated = this->initial_net_weight_allocation; _resource_config.set(config, get_self()); - _appsv2.emplace(get_self(), [&](auto &app_itr) { - app_itr.account_name = random_name; - app_itr.json_data = json_data; - app_itr.version = 2; - app_itr.username_hash = username_hash; - app_itr.origin = origin; + // Set resource limits: cpu and net to initial, ram to 0 + eosio::set_resource_limits(random_name, 0, this->initial_cpu_weight_allocation, this->initial_net_weight_allocation); + + // Register in appsv3 + _appsv3.emplace(get_self(), [&](auto &row) { + row.account_name = random_name; + row.json_data = json_data; + row.version = 3; + row.username = username; + row.origin = origin; + row.plan = static_cast(plan_t::plan_basic); }); + // Set account type tonomy::account_type_table account_type(get_self(), get_self().value); account_type.emplace(get_self(), [&](auto &row) { row.account_name = random_name; @@ -67,13 +75,6 @@ void apps::newapp(string json_data, }); } -void apps::eraseoldapps() -{ - eosio::require_auth(get_self()); - while (_apps.begin() != _apps.end()) { - _apps.erase(_apps.begin()); - } -} void apps::check_app_username(const checksum256 &username_hash) { @@ -94,66 +95,44 @@ void apps::check_app_origin(const string &origin) } } -void apps::adminsetapp(name account_name, +void apps::admnupdapp(name account_name, string json_data, - checksum256 username_hash, - string origin) + string username, + string origin, + uint8_t plan) { - eosio::require_auth(get_self()); - eosio::check(is_account(account_name), "Account does not exist"); + require_auth(get_self()); + check(is_account(account_name), "Account does not exist"); - tonomy::account_type_table account_type(get_self(), get_self().value); - auto itr = account_type.find(account_name.value); - if (itr == account_type.end()) { - account_type.emplace(get_self(), [&](auto &row) { - row.account_name = account_name; - row.acc_type = enum_account_type::App; - row.version = 1; - }); - } + auto itr = _appsv3.find(account_name.value); + check(itr != _appsv3.end(), "App does not exist; use admncrtapp to create"); - auto apps_itr = _appsv2.find(account_name.value); - - if (apps_itr != _appsv2.end()) { - if (apps_itr->origin != origin) { - check_app_origin(origin); - } - if (apps_itr->username_hash != username_hash) { - check_app_username(username_hash); - } - _appsv2.modify(apps_itr, get_self(), [&](auto &app_itr) { - app_itr.account_name = account_name; - app_itr.origin = origin; - app_itr.username_hash = username_hash; - app_itr.json_data = json_data; - app_itr.version = 2; - }); - } else { - check_app_username(username_hash); - check_app_origin(origin); - _appsv2.emplace(get_self(), [&](auto &app_itr) { - app_itr.account_name = account_name; - app_itr.origin = origin; - app_itr.username_hash = username_hash; - app_itr.json_data = json_data; - app_itr.version = 2; - }); + // uniqueness checks if changed + if (itr->username != username) { + auto uidx = _appsv3.get_index<"usernamehash"_n>(); + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + check(uidx.find(username_hash) == uidx.end(), "Username already taken"); } + if (itr->origin != origin) { + auto oidx = _appsv3.get_index<"originhash"_n>(); + checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); + } + _appsv3.modify(itr, get_self(), [&](auto &row) { + row.json_data = json_data; + row.username = username; + row.origin = origin; + row.plan = plan; + row.version = 3; + }); } -void apps::deleteapp(name account_name) +void apps::admndelapp(name account_name) { - eosio::require_auth(get_self()); - - auto itr1 = _apps.find(account_name.value); - if (itr1 != _apps.end()) { - _apps.erase(itr1); - } - - auto itr2 = _appsv2.find(account_name.value); - if (itr2 != _appsv2.end()) { - _appsv2.erase(itr2); - } + require_auth(get_self()); + auto itr = _appsv3.find(account_name.value); + check(itr != _appsv3.end(), "App does not exist"); + _appsv3.erase(itr); } void apps::loginwithapp(name account, @@ -161,8 +140,8 @@ void apps::loginwithapp(name account, name parent, public_key key) { - auto app_itr = _appsv2.find(app.value); - check(app_itr != _appsv2.end(), "App does not exist"); + auto app_itr = _appsv3.find(app.value); + check(app_itr != _appsv3.end(), "App does not exist"); authority authority = create_authority_with_key(key); @@ -170,12 +149,12 @@ void apps::loginwithapp(name account, updateauthaction.send(account, app, parent, authority); } -void apps::buyram(const name &dao_owner, const name &app, const asset &quant) +void apps::scbuyram(const name &account_name, const asset &quant) { - require_auth(app); + require_auth(account_name); tonomy::account_type_table account_type(get_self(), get_self().value); - auto itr = account_type.find(app.value); + auto itr = account_type.find(account_name.value); eosio::check(itr != account_type.end(), "Could not find account"); eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); @@ -184,11 +163,8 @@ void apps::buyram(const name &dao_owner, const name &app, const asset &quant) tonomy::resource_config_table config_table(get_self(), get_self().value); tonomy::resource_config config; - if (config_table.exists()) { - config = config_table.get(); - } else { - eosio::check(false, "Resource config does not exist"); - } + eosio::check(config_table.exists(), "Resource config does not exist"); + config = config_table.get(); eosio::check(config.ram_price != 0, "Failed to retrieve ram_price from resource config"); eosio::check(config.ram_fee != 0, "Failed to retrieve ram_fee from resource config"); @@ -203,23 +179,23 @@ void apps::buyram(const name &dao_owner, const name &app, const asset &quant) config_table.set(config, get_self()); int64_t myRAM, myNET, myCPU; - eosio::get_resource_limits(app, myRAM, myNET, myCPU); - eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"buyram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM + ram_purchase, "}]"); - eosio::set_resource_limits(app, myRAM + ram_purchase, myNET, myNET); + eosio::get_resource_limits(account_name, myRAM, myNET, myCPU); + eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"scbuyram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM + ram_purchase, "}]\"}"); + eosio::set_resource_limits(account_name, myRAM + ram_purchase, myNET, myNET); - eosio::action(permission_level{dao_owner, "active"_n}, + eosio::action(permission_level{account_name, "active"_n}, tonomy::token_contract_name, "transfer"_n, - std::make_tuple(dao_owner, native::governance_name, quant, std::string("buy ram"))) + std::make_tuple(account_name, native::governance_name, quant, std::string("buy ram"))) .send(); } -void apps::sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant) +void apps::scsellram(const name &account_name, const asset &quant) { - require_auth(app); + require_auth(account_name); tonomy::account_type_table account_type(get_self(), get_self().value); - auto itr = account_type.find(app.value); + auto itr = account_type.find(account_name.value); eosio::check(itr != account_type.end(), "Could not find account"); eosio::check(itr->acc_type == enum_account_type::App, "Only apps can buy and sell RAM"); @@ -239,15 +215,15 @@ void apps::sellram(eosio::name dao_owner, eosio::name app, eosio::asset quant) resource_config_singleton.set(config, get_self()); int64_t myRAM, myNET, myCPU; - eosio::get_resource_limits(app, myRAM, myNET, myCPU); - eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"sellram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM - ram_sold, "}]"); + eosio::get_resource_limits(account_name, myRAM, myNET, myCPU); + eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"scsellram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM - ram_sold, "}]\"}"); eosio::check(myRAM - ram_sold >= 0, "Account cannot have less than 0 RAM"); - eosio::set_resource_limits(app, myRAM - ram_sold, myNET, myNET); + eosio::set_resource_limits(account_name, myRAM - ram_sold, myNET, myNET); eosio::action(permission_level{get_self(), "active"_n}, tonomy::token_contract_name, "transfer"_n, - std::make_tuple(native::governance_name, dao_owner, eosio::asset(ram_sold, tonomy::system_resource_currency), std::string("sell ram"))) + std::make_tuple(native::governance_name, account_name, eosio::asset(ram_sold, tonomy::system_resource_currency), std::string("sell ram"))) .send(); } @@ -275,4 +251,157 @@ const name apps::get_app_permission_by_username(string username, name contract_n return username_itr->account_name; } +void apps::appcreate(name creator, + string json_data, + string username, + string origin) +{ + require_auth(creator); + + // Uniqueness checks for username and origin + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + { + auto uidx = _appsv3.get_index<"usernamehash"_n>(); + check(uidx.find(username_hash) == uidx.end(), "Username already taken"); + } + checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); + { + auto oidx = _appsv3.get_index<"originhash"_n>(); + check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); + } + + // Generate random account name from username and json_data hashes + auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); + const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); + + // Create account with owner=gov.tmy, active=creator + authority owner_authority = create_authority_with_account(app_controller_account); + authority active_authority = create_authority_with_account(creator); + active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); + + newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); + newaccountaction.send(get_self(), random_name, owner_authority, active_authority); + + // Update resource config + tonomy::resource_config_table _resource_config(get_self(), get_self().value); + auto config = _resource_config.get(); + config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; + config.total_net_weight_allocated = this->initial_net_weight_allocation; + _resource_config.set(config, get_self()); + + // Set resource limits: cpu and net to initial, ram to 0 + eosio::set_resource_limits(random_name, 0, this->initial_cpu_weight_allocation, this->initial_net_weight_allocation); + + // Register in appsv3 + _appsv3.emplace(get_self(), [&](auto &row) { + row.account_name = random_name; + row.json_data = json_data; + row.version = 3; + row.username = username; + row.origin = origin; + row.plan = static_cast(plan_t::plan_basic); + }); + + // Set account type + tonomy::account_type_table account_type(get_self(), get_self().value); + account_type.emplace(get_self(), [&](auto &row) { + row.account_name = random_name; + row.acc_type = enum_account_type::App; + row.version = 1; + }); +} + +void apps::appupdate(name account_name, + string json_data, + string username) +{ + require_auth(account_name); + auto itr = _appsv3.find(account_name.value); + check(itr != _appsv3.end(), "App does not exist"); + + // If username changed, ensure uniqueness + if (itr->username != username) { + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); + auto uidx = _appsv3.get_index<"usernamehash"_n>(); + check(uidx.find(username_hash) == uidx.end(), "Username already taken"); + } + + _appsv3.modify(itr, get_self(), [&](auto &row) { + row.json_data = json_data; + row.username = username; + // keep origin unchanged here; only admin can change origin + row.version = 3; + }); +} + +void apps::appupdplan(name account_name, + uint8_t plan) +{ + // Plan updates assumed admin-governed + require_auth(get_self()); + auto itr = _appsv3.find(account_name.value); + check(itr != _appsv3.end(), "App does not exist"); + _appsv3.modify(itr, get_self(), [&](auto &row) { + row.plan = plan; + }); +} + +void apps::scdeploy(name account_name, + uint8_t vmtype, + uint8_t vmversion, + const std::vector &code, + const std::vector &abi, + string source_code_url) +{ + require_auth(get_self()); + // TODO: Implement smart contract deployment + check(false, "scdeploy not yet implemented"); +} + +void apps::scupdate(name account_name, + uint8_t vmtype, + uint8_t vmversion, + const std::vector &code, + const std::vector &abi, + string source_code_url) +{ + require_auth(get_self()); + // TODO: Implement smart contract update + check(false, "scupdate not yet implemented"); +} + +void apps::appaddkey(name account_name, + public_key key) +{ + require_auth(get_self()); + // TODO: Implement key addition to app account's active permission + check(false, "appaddkey not yet implemented"); +} + +void apps::appremkey(name account_name, + public_key key) +{ + require_auth(get_self()); + // TODO: Implement key removal from app account's active permission + check(false, "appremkey not yet implemented"); +} + +void apps::admnmigapp(name account_name, + string username, + uint8_t plan, + public_key key) +{ + require_auth(get_self()); + // TODO: Implement V2 to V3 app migration + check(false, "admnmigapp not yet implemented"); +} + +void apps::admnmigsc(name account_name, + string source_code_url) +{ + require_auth(get_self()); + // TODO: Implement smart contract metadata migration + check(false, "admnmigsc not yet implemented"); +} + } // namespace tonomysystem From 5fbb0a76a592d15b7cc3dff34b0eafd6d8499dab Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 20:29:20 +0100 Subject: [PATCH 5/8] fix: sorted out a few consistency things --- contracts/tonomy/include/tonomy/apps.hpp | 3 +++ contracts/tonomy/src/apps.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index fe12d66..bce1da2 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -18,6 +18,9 @@ namespace tonomysystem using eosio::asset; using eosio::check; using eosio::checksum256; + + // Constants + static constexpr eosio::name app_controller_account = "gov.tmy"_n; using eosio::ignore; using eosio::name; using eosio::permission_level; diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index ec508da..cf147e2 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -53,8 +53,8 @@ void apps::admncrtapp(string json_data, config.total_net_weight_allocated = this->initial_net_weight_allocation; _resource_config.set(config, get_self()); - // Set resource limits: cpu and net to initial, ram to 0 - eosio::set_resource_limits(random_name, 0, this->initial_cpu_weight_allocation, this->initial_net_weight_allocation); + // Set resource limits: ram=0, net=initial, cpu=initial + eosio::set_resource_limits(random_name, 0, this->initial_net_weight_allocation, this->initial_cpu_weight_allocation); // Register in appsv3 _appsv3.emplace(get_self(), [&](auto &row) { @@ -78,7 +78,7 @@ void apps::admncrtapp(string json_data, void apps::check_app_username(const checksum256 &username_hash) { - auto apps_by_username_hash_itr = _appsv2.get_index<"usernamehash"_n>(); + auto apps_by_username_hash_itr = _appsv3.get_index<"usernamehash"_n>(); const auto username_itr = apps_by_username_hash_itr.find(username_hash); if (username_itr != apps_by_username_hash_itr.end()) { throwError("TCON1001", "This app username is already taken"); @@ -88,7 +88,7 @@ void apps::check_app_username(const checksum256 &username_hash) void apps::check_app_origin(const string &origin) { auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - auto apps_by_origin_hash_itr = _appsv2.get_index<"originhash"_n>(); + auto apps_by_origin_hash_itr = _appsv3.get_index<"originhash"_n>(); const auto origin_itr = apps_by_origin_hash_itr.find(origin_hash); if (origin_itr != apps_by_origin_hash_itr.end()) { throwError("TCON1002", "This app origin is already taken"); @@ -181,7 +181,7 @@ void apps::scbuyram(const name &account_name, const asset &quant) int64_t myRAM, myNET, myCPU; eosio::get_resource_limits(account_name, myRAM, myNET, myCPU); eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"scbuyram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM + ram_purchase, "}]\"}"); - eosio::set_resource_limits(account_name, myRAM + ram_purchase, myNET, myNET); + eosio::set_resource_limits(account_name, myRAM + ram_purchase, myNET, myCPU); eosio::action(permission_level{account_name, "active"_n}, tonomy::token_contract_name, @@ -218,7 +218,7 @@ void apps::scsellram(const name &account_name, const asset &quant) eosio::get_resource_limits(account_name, myRAM, myNET, myCPU); eosio::print("{\"event_log\":{\"account\":\"tonomy\",\"action\":\"scsellram\"},\"time\":\"", eosio::current_time_point().to_string(), "Z\",\"events\":[{\"previous\":", myRAM, ",\"current\":", myRAM - ram_sold, "}]\"}"); eosio::check(myRAM - ram_sold >= 0, "Account cannot have less than 0 RAM"); - eosio::set_resource_limits(account_name, myRAM - ram_sold, myNET, myNET); + eosio::set_resource_limits(account_name, myRAM - ram_sold, myNET, myCPU); eosio::action(permission_level{get_self(), "active"_n}, tonomy::token_contract_name, @@ -289,8 +289,8 @@ void apps::appcreate(name creator, config.total_net_weight_allocated = this->initial_net_weight_allocation; _resource_config.set(config, get_self()); - // Set resource limits: cpu and net to initial, ram to 0 - eosio::set_resource_limits(random_name, 0, this->initial_cpu_weight_allocation, this->initial_net_weight_allocation); + // Set resource limits: ram=0, net=initial, cpu=initial + eosio::set_resource_limits(random_name, 0, this->initial_net_weight_allocation, this->initial_cpu_weight_allocation); // Register in appsv3 _appsv3.emplace(get_self(), [&](auto &row) { From 4e7c8dee613dc5abc953c408d0c32bd1ac934dcd Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Tue, 16 Dec 2025 20:58:53 +0100 Subject: [PATCH 6/8] feat: username validation --- contracts/tonomy/include/tonomy/apps.hpp | 5 +++++ contracts/tonomy/src/apps.cpp | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index bce1da2..74e42e5 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -379,6 +379,11 @@ namespace tonomysystem */ void check_app_origin(const string &origin); + /** + * Validate username characters against allowed set [A-Za-z0-9_-] + */ + void check_app_username_chars(const string &username); + /** * Check if the raw username is already taken in appsv3 * diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index cf147e2..b058301 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -22,6 +22,8 @@ void apps::admncrtapp(string json_data, { require_auth(get_self()); + check_app_username_chars(username); + // Uniqueness checks for username and origin checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); { @@ -95,6 +97,19 @@ void apps::check_app_origin(const string &origin) } } +void apps::check_app_username_chars(const string &username) +{ + for (const char c : username) { + const bool is_upper = c >= 'A' && c <= 'Z'; + const bool is_lower = c >= 'a' && c <= 'z'; + const bool is_digit = c >= '0' && c <= '9'; + const bool is_allowed_symbol = c == '_' || c == '-'; + if (!(is_upper || is_lower || is_digit || is_allowed_symbol)) { + check(false, "Username may only contain A-Z, a-z, 0-9, '_' or '-' characters"); + } + } +} + void apps::admnupdapp(name account_name, string json_data, string username, @@ -107,8 +122,9 @@ void apps::admnupdapp(name account_name, auto itr = _appsv3.find(account_name.value); check(itr != _appsv3.end(), "App does not exist; use admncrtapp to create"); - // uniqueness checks if changed + // validate and uniqueness checks if changed if (itr->username != username) { + check_app_username_chars(username); auto uidx = _appsv3.get_index<"usernamehash"_n>(); checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); check(uidx.find(username_hash) == uidx.end(), "Username already taken"); @@ -259,6 +275,7 @@ void apps::appcreate(name creator, require_auth(creator); // Uniqueness checks for username and origin + check_app_username_chars(username); checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); { auto uidx = _appsv3.get_index<"usernamehash"_n>(); @@ -321,6 +338,7 @@ void apps::appupdate(name account_name, // If username changed, ensure uniqueness if (itr->username != username) { + check_app_username_chars(username); checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); auto uidx = _appsv3.get_index<"usernamehash"_n>(); check(uidx.find(username_hash) == uidx.end(), "Username already taken"); From f7c6dc05b18983bfb69016348a5b9342a6986c6c Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Wed, 17 Dec 2025 00:02:48 +0100 Subject: [PATCH 7/8] feat: creator for admin create app --- contracts/tonomy/include/tonomy/apps.hpp | 2 ++ contracts/tonomy/src/apps.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index 74e42e5..f30536c 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -300,11 +300,13 @@ namespace tonomysystem /** * Admin: create or set an app record * + * @param creator - account that will own the active permission (in addition to code permission) * @param json_data - JSON with display details * @param username - raw username (unique) * @param origin - domain (unique) */ [[eosio::action]] void admncrtapp( + name creator, string json_data, string username, string origin); diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index b058301..3d56aa8 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -16,12 +16,15 @@ apps::apps(name receiver, name code, eosio::datastream ds) _smartcontracts(receiver, receiver.value) {} // Admin create app with random account name -void apps::admncrtapp(string json_data, +void apps::admncrtapp(name creator, + string json_data, string username, string origin) { require_auth(get_self()); + check(is_account(creator), "Creator account does not exist"); + check_app_username_chars(username); // Uniqueness checks for username and origin @@ -40,9 +43,9 @@ void apps::admncrtapp(string json_data, auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); - // Create account with owner=gov.tmy, active=contract + // Create account with owner=gov.tmy, active=creator authority owner_authority = create_authority_with_account(app_controller_account); - authority active_authority = create_authority_with_account(get_self()); + authority active_authority = create_authority_with_account(creator); active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); From 2b1e002b91006abfbad39fb9c3d391975702dacf Mon Sep 17 00:00:00 2001 From: Jack Tanner Date: Wed, 17 Dec 2025 10:46:55 +0100 Subject: [PATCH 8/8] feat: fixed some issues in the logic, and refactors --- contracts/tonomy/include/tonomy/apps.hpp | 57 ++++++++- contracts/tonomy/src/apps.cpp | 151 ++++++++++------------- 2 files changed, 120 insertions(+), 88 deletions(-) diff --git a/contracts/tonomy/include/tonomy/apps.hpp b/contracts/tonomy/include/tonomy/apps.hpp index f30536c..fe97ee9 100644 --- a/contracts/tonomy/include/tonomy/apps.hpp +++ b/contracts/tonomy/include/tonomy/apps.hpp @@ -306,7 +306,8 @@ namespace tonomysystem * @param origin - domain (unique) */ [[eosio::action]] void admncrtapp( - name creator, + name account_name, + name creator, string json_data, string username, string origin); @@ -327,6 +328,24 @@ namespace tonomysystem string origin, uint8_t plan); + /** + * Admin: register app data for an existing account + * Similar to admncrtapp but does not create the account or set resource limits. + * Used when the account already exists. + * + * @param account_name - the app account name (must already exist) + * @param creator - the creator account (used for active permission via updateauth) + * @param json_data - JSON with display details + * @param username - raw username (unique) + * @param origin - domain (unique) + */ + [[eosio::action]] void adminregapp( + name account_name, + name creator, + string json_data, + string username, + string origin); + /** * Admin: delete an app record * @@ -362,6 +381,7 @@ namespace tonomysystem using admncrtapp_action = action_wrapper<"admncrtapp"_n, &apps::admncrtapp>; using admnupdapp_action = action_wrapper<"admnupdapp"_n, &apps::admnupdapp>; + using adminregapp_action = action_wrapper<"adminregapp"_n, &apps::adminregapp>; using admndelapp_action = action_wrapper<"admndelapp"_n, &apps::admndelapp>; using admnmigapp_action = action_wrapper<"admnmigapp"_n, &apps::admnmigapp>; using admnmigsc_action = action_wrapper<"admnmigsc"_n, &apps::admnmigsc>; @@ -372,20 +392,39 @@ namespace tonomysystem * * @param username_hash - hash of the username of the account */ - void check_app_username(const checksum256 &username_hash); + // Helper: ensure the provided username is not already taken + void check_username_is_unique(const string &username); /** * Check if the app origin is already taken * * @param origin - domain associated with the app */ - void check_app_origin(const string &origin); + void check_app_origin_is_unique(const string &origin); /** * Validate username characters against allowed set [A-Za-z0-9_-] + * + * @param username - raw username string */ void check_app_username_chars(const string &username); + /** + * Update resource config and set resource limits for an account + * + * @param account_name - the account to set resource limits for + */ + void update_resource_config_and_limits(name account_name); + + /** + * Create a new account with specified owner and active authorities + * + * @param account_name - the new account name + * @param owner_account - the account name for owner permission + * @param active_account - the account name for active permission + */ + void create_app_account(name account_name, name owner_account, name active_account); + /** * Check if the raw username is already taken in appsv3 * @@ -399,5 +438,17 @@ namespace tonomysystem auto itr = idx.find(username_hash); check(itr == idx.end(), "Username already taken"); } + + /** + * Register app data in appsv3 table and update authority + * Common logic used by both admncrtapp and adminregapp + * + * @param account_name - the app account name + * @param creator - the creator account for active permission + * @param json_data - JSON with display details + * @param username - raw username string (unique) + * @param origin - domain (unique) + */ + void register_app_data(name account_name, name creator, const string &json_data, const string &username, const string &origin); }; } \ No newline at end of file diff --git a/contracts/tonomy/src/apps.cpp b/contracts/tonomy/src/apps.cpp index 3d56aa8..f0b51ce 100644 --- a/contracts/tonomy/src/apps.cpp +++ b/contracts/tonomy/src/apps.cpp @@ -16,54 +16,42 @@ apps::apps(name receiver, name code, eosio::datastream ds) _smartcontracts(receiver, receiver.value) {} // Admin create app with random account name -void apps::admncrtapp(name creator, +void apps::admncrtapp(name account_name, + name creator, string json_data, string username, string origin) { require_auth(get_self()); + check(is_account(creator), "Creator account does not exist"); + create_app_account(account_name, app_controller_account, creator); + register_app_data(account_name, creator, json_data, username, origin); +} +// Admin register app data for an existing account (without creating account) +void apps::adminregapp(name account_name, + name creator, + string json_data, + string username, + string origin) +{ + require_auth(get_self()); + check(is_account(account_name), "Account does not exist"); check(is_account(creator), "Creator account does not exist"); + register_app_data(account_name, creator, json_data, username, origin); +} +// Private helper: Register app data, account type, and update authority +void apps::register_app_data(name account_name, name creator, const string &json_data, const string &username, const string &origin) +{ check_app_username_chars(username); - - // Uniqueness checks for username and origin - checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - { - auto uidx = _appsv3.get_index<"usernamehash"_n>(); - check(uidx.find(username_hash) == uidx.end(), "Username already taken"); - } - checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - { - auto oidx = _appsv3.get_index<"originhash"_n>(); - check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); - } - - // Generate random account name from username and json_data hashes - auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); - const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); - - // Create account with owner=gov.tmy, active=creator - authority owner_authority = create_authority_with_account(app_controller_account); - authority active_authority = create_authority_with_account(creator); - active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); - - newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); - newaccountaction.send(get_self(), random_name, owner_authority, active_authority); - - // Update resource config - tonomy::resource_config_table _resource_config(get_self(), get_self().value); - auto config = _resource_config.get(); - config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; - config.total_net_weight_allocated = this->initial_net_weight_allocation; - _resource_config.set(config, get_self()); - - // Set resource limits: ram=0, net=initial, cpu=initial - eosio::set_resource_limits(random_name, 0, this->initial_net_weight_allocation, this->initial_cpu_weight_allocation); + check_username_is_unique(username); + check_app_origin_is_unique(origin); + update_resource_config_and_limits(account_name); // Register in appsv3 _appsv3.emplace(get_self(), [&](auto &row) { - row.account_name = random_name; + row.account_name = account_name; row.json_data = json_data; row.version = 3; row.username = username; @@ -74,15 +62,15 @@ void apps::admncrtapp(name creator, // Set account type tonomy::account_type_table account_type(get_self(), get_self().value); account_type.emplace(get_self(), [&](auto &row) { - row.account_name = random_name; + row.account_name = account_name; row.acc_type = enum_account_type::App; row.version = 1; }); } - -void apps::check_app_username(const checksum256 &username_hash) +void apps::check_username_is_unique(const string &username) { + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); auto apps_by_username_hash_itr = _appsv3.get_index<"usernamehash"_n>(); const auto username_itr = apps_by_username_hash_itr.find(username_hash); if (username_itr != apps_by_username_hash_itr.end()) { @@ -90,7 +78,7 @@ void apps::check_app_username(const checksum256 &username_hash) } } -void apps::check_app_origin(const string &origin) +void apps::check_app_origin_is_unique(const string &origin) { auto origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); auto apps_by_origin_hash_itr = _appsv3.get_index<"originhash"_n>(); @@ -113,6 +101,30 @@ void apps::check_app_username_chars(const string &username) } } +void apps::update_resource_config_and_limits(name account_name) +{ + // Update resource config + tonomy::resource_config_table _resource_config(get_self(), get_self().value); + auto config = _resource_config.get(); + config.total_cpu_weight_allocated += this->initial_cpu_weight_allocation; + config.total_net_weight_allocated += this->initial_net_weight_allocation; + _resource_config.set(config, get_self()); + + // Set resource limits: ram=0, net=initial, cpu=initial + eosio::set_resource_limits(account_name, 0, this->initial_net_weight_allocation, this->initial_cpu_weight_allocation); +} + +void apps::create_app_account(name account_name, name owner_account, name active_account) +{ + // Create account with specified owner and active authorities + authority owner_authority = create_authority_with_account(owner_account); + authority active_authority = create_authority_with_account(active_account); + active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); + + newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); + newaccountaction.send(get_self(), account_name, owner_authority, active_authority); +} + void apps::admnupdapp(name account_name, string json_data, string username, @@ -120,7 +132,6 @@ void apps::admnupdapp(name account_name, uint8_t plan) { require_auth(get_self()); - check(is_account(account_name), "Account does not exist"); auto itr = _appsv3.find(account_name.value); check(itr != _appsv3.end(), "App does not exist; use admncrtapp to create"); @@ -128,14 +139,10 @@ void apps::admnupdapp(name account_name, // validate and uniqueness checks if changed if (itr->username != username) { check_app_username_chars(username); - auto uidx = _appsv3.get_index<"usernamehash"_n>(); - checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - check(uidx.find(username_hash) == uidx.end(), "Username already taken"); + check_username_is_unique(username); } if (itr->origin != origin) { - auto oidx = _appsv3.get_index<"originhash"_n>(); - checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); + check_app_origin_is_unique(origin); } _appsv3.modify(itr, get_self(), [&](auto &row) { row.json_data = json_data; @@ -276,41 +283,17 @@ void apps::appcreate(name creator, string origin) { require_auth(creator); - - // Uniqueness checks for username and origin check_app_username_chars(username); - checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - { - auto uidx = _appsv3.get_index<"usernamehash"_n>(); - check(uidx.find(username_hash) == uidx.end(), "Username already taken"); - } - checksum256 origin_hash = eosio::sha256(origin.c_str(), std::strlen(origin.c_str())); - { - auto oidx = _appsv3.get_index<"originhash"_n>(); - check(oidx.find(origin_hash) == oidx.end(), "Origin already taken"); - } + check_username_is_unique(username); + check_app_origin_is_unique(origin); // Generate random account name from username and json_data hashes + checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); auto json_hash = eosio::sha256(json_data.c_str(), std::strlen(json_data.c_str())); const eosio::name random_name = random_account_name(username_hash, json_hash, enum_account_type::App); - // Create account with owner=gov.tmy, active=creator - authority owner_authority = create_authority_with_account(app_controller_account); - authority active_authority = create_authority_with_account(creator); - active_authority.accounts.push_back({.permission = create_eosio_code_permission_level(get_self()), .weight = 1}); - - newaccount_action newaccountaction("eosio"_n, {get_self(), "active"_n}); - newaccountaction.send(get_self(), random_name, owner_authority, active_authority); - - // Update resource config - tonomy::resource_config_table _resource_config(get_self(), get_self().value); - auto config = _resource_config.get(); - config.total_cpu_weight_allocated = this->initial_cpu_weight_allocation; - config.total_net_weight_allocated = this->initial_net_weight_allocation; - _resource_config.set(config, get_self()); - - // Set resource limits: ram=0, net=initial, cpu=initial - eosio::set_resource_limits(random_name, 0, this->initial_net_weight_allocation, this->initial_cpu_weight_allocation); + create_app_account(random_name, app_controller_account, creator); + update_resource_config_and_limits(random_name); // Register in appsv3 _appsv3.emplace(get_self(), [&](auto &row) { @@ -342,9 +325,7 @@ void apps::appupdate(name account_name, // If username changed, ensure uniqueness if (itr->username != username) { check_app_username_chars(username); - checksum256 username_hash = eosio::sha256(username.c_str(), std::strlen(username.c_str())); - auto uidx = _appsv3.get_index<"usernamehash"_n>(); - check(uidx.find(username_hash) == uidx.end(), "Username already taken"); + check_username_is_unique(username); } _appsv3.modify(itr, get_self(), [&](auto &row) { @@ -359,7 +340,7 @@ void apps::appupdplan(name account_name, uint8_t plan) { // Plan updates assumed admin-governed - require_auth(get_self()); + require_auth(account_name); auto itr = _appsv3.find(account_name.value); check(itr != _appsv3.end(), "App does not exist"); _appsv3.modify(itr, get_self(), [&](auto &row) { @@ -374,7 +355,7 @@ void apps::scdeploy(name account_name, const std::vector &abi, string source_code_url) { - require_auth(get_self()); + require_auth(account_name); // TODO: Implement smart contract deployment check(false, "scdeploy not yet implemented"); } @@ -386,7 +367,7 @@ void apps::scupdate(name account_name, const std::vector &abi, string source_code_url) { - require_auth(get_self()); + require_auth(account_name); // TODO: Implement smart contract update check(false, "scupdate not yet implemented"); } @@ -394,7 +375,7 @@ void apps::scupdate(name account_name, void apps::appaddkey(name account_name, public_key key) { - require_auth(get_self()); + require_auth(account_name); // TODO: Implement key addition to app account's active permission check(false, "appaddkey not yet implemented"); } @@ -402,7 +383,7 @@ void apps::appaddkey(name account_name, void apps::appremkey(name account_name, public_key key) { - require_auth(get_self()); + require_auth(account_name); // TODO: Implement key removal from app account's active permission check(false, "appremkey not yet implemented"); } @@ -412,7 +393,7 @@ void apps::admnmigapp(name account_name, uint8_t plan, public_key key) { - require_auth(get_self()); + require_auth(account_name); // TODO: Implement V2 to V3 app migration check(false, "admnmigapp not yet implemented"); }