chore(CPP): Enhance type safety using phantom types

Important changes:

- Converted the easy to mix strings to named types as phantom types
- Renamed `AccountDto`s to `MultiAccount` to better match the status-go
domain knowledge.
- Renamed MultiAccount to ChatOrWalletAccount to better match its multi
purpose and don't confuse with the MultiAccount domain knowledge.
- Remove libs/CMakeLists.txt

Note: Tried to use the fluent::NamedType but it doesn't work with
nlohmann_json, gave up finding why. Therefore I extracted only
the needed functionality for the simple types we use.

Updates: #6321
This commit is contained in:
Stefan 2022-07-13 18:45:18 +02:00 committed by Stefan Dunca
parent a130681dd5
commit 16b866ccbd
53 changed files with 383 additions and 289 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ noBackup/
*.log
.update.timestamp
.vscode
*.code-workspace
.tours
bin/
/bottles/

View File

@ -27,9 +27,18 @@ endif()
# status-desktop application
add_subdirectory(vendor)
add_subdirectory(libs)
add_subdirectory(libs/ApplicationCore)
add_subdirectory(libs/Assets)
add_subdirectory(libs/Helpers)
add_subdirectory(libs/Onboarding)
add_subdirectory(libs/StatusGoQt)
add_subdirectory(libs/StatusQ)
add_subdirectory(app)
add_subdirectory(test/libs/StatusGoQt)
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
add_subdirectory(resources)
add_subdirectory(ui/imports/assets)

View File

@ -32,6 +32,9 @@ qt6_add_qml_module(${PROJECT_NAME}
qml/Status/Application/MainShortcuts.qml
qml/Status/Application/StatusWindow.qml
SOURCES
res/app.qrc
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application
)
@ -47,7 +50,6 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_SOURCE_DIR=${CMAKE_SOUR
add_subdirectory(qml/Status/Application/Navigation)
add_subdirectory(src)
add_subdirectory(res)
include(${CMAKE_SOURCE_DIR}/cmake/platform_specific.cmake)
string(TOLOWER ${PROJECT_ORGANIZATION_NAME} URL_ORGANIZATION_NAME)

View File

@ -14,7 +14,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Application.Navigation
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusNavigationBar.qml
StatusNavigationButton.qml

View File

@ -1,15 +0,0 @@
# TODO workaround until Qt6 API is clarified
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/app.qrc
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
# TODO find out why it isn't working
#
#qt6_target_qml_sources(${PROJECT_NAME}
# RESOURCES
# qtquickcontrols2.conf
# PREFIX ""
#)

View File

@ -20,7 +20,6 @@ qt6_add_qml_module(Assets
URI Status.Assets
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
qml/Status/Assets/Resources.qml

View File

@ -1,6 +0,0 @@
add_subdirectory(ApplicationCore)
add_subdirectory(Assets)
add_subdirectory(Helpers)
add_subdirectory(Onboarding)
add_subdirectory(StatusGoQt)
add_subdirectory(StatusQ)

View File

@ -71,5 +71,6 @@ target_sources(Helpers
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/helpers.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/NamedType.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/Singleton.h
)

View File

@ -0,0 +1,70 @@
#pragma once
#include <nlohmann/json.hpp>
#include <type_traits>
#include <utility>
using json = nlohmann::json;
namespace Status::Helpers {
template<typename T>
using IsNotReference = typename std::enable_if<!std::is_reference<T>::value, void>::type;
/// Featureless version of https://github.com/joboccara/NamedType that works with nlohmann::json
template <typename T, typename Parameter>
class NamedType
{
public:
using UnderlyingType = T;
// constructor
explicit constexpr NamedType(T const& value) : m_value(value) {}
template<typename T_ = T, typename = IsNotReference<T_>>
explicit constexpr NamedType(T&& value) : m_value(std::move(value)) {}
explicit constexpr NamedType() = default;
// get
constexpr T& get() { return m_value; }
constexpr std::remove_reference_t<T> const& get() const {return m_value; }
bool operator<(const NamedType<T, Parameter> &) const = default;
bool operator>(const NamedType<T, Parameter> &) const = default;
bool operator<=(const NamedType<T, Parameter> &) const = default;
bool operator>=(const NamedType<T, Parameter> &) const = default;
bool operator==(const NamedType<T, Parameter> &) const = default;
bool operator!=(const NamedType<T, Parameter> &) const = default;
private:
T m_value;
};
template <typename T, typename P>
void to_json(json& j, const NamedType<T, P>& p) {
j = p.get();
}
template <typename T, typename P>
void from_json(const json& j, NamedType<T, P>& p) {
p = NamedType<T, P>{j.get<T>()};
}
}
namespace std
{
template <typename T, typename Parameter>
struct hash<Status::Helpers::NamedType<T, Parameter>>
{
using NamedType = Status::Helpers::NamedType<T, Parameter>;
using checkIfHashable = typename std::enable_if<NamedType::is_hashable, void>::type;
size_t operator()(NamedType const& x) const
{
return std::hash<T>()(x.get());
}
};
}

View File

@ -4,13 +4,8 @@ namespace fs = std::filesystem;
namespace Status {
QString toQString(const std::string &str)
{
return QString::fromStdString(str);
}
QString toQString(const fs::path &path) {
return toQString(path.string());
return QString::fromStdString(path.string());
}
fs::path toPath(const QString &pathStr) {

View File

@ -12,8 +12,8 @@ using json = nlohmann::json;
namespace Status {
QString toQString(const std::string& str);
QString toQString(const std::filesystem::path& path);
std::filesystem::path toPath(const QString& pathStr);
} // namespace Status
@ -42,4 +42,15 @@ struct adl_serializer<QColor> {
}
};
template<typename T>
struct adl_serializer<std::optional<T>> {
static void to_json(json& j, const std::optional<T>& opt) {
j = opt.value();
}
static void from_json(const json& j, std::optional<T>& opt) {
opt.emplace(j.get<T>());
}
};
} // namespace nlohmann

View File

@ -37,6 +37,9 @@ add_subdirectory(src)
add_subdirectory(tests)
target_link_libraries(Onboarding
PUBLIC
Status::StatusGoQt
PRIVATE
Qt6::Quick
Qt6::Qml
@ -45,7 +48,6 @@ target_link_libraries(Onboarding
Status::ApplicationCore
Status::Helpers
Status::StatusGoQt
Status::StatusGoConfig
)

View File

@ -11,9 +11,6 @@ SetupNewProfilePageBase {
TempTextInput {
id: confirmPasswordInput
// TODO: remove this developer helper
text: qsTr("1234567890")
width: 416
height: 44

View File

@ -26,12 +26,12 @@ getDataFromFile(const fs::path &path)
return data;
}
namespace Status::Onboarding
{
namespace StatusGo = Status::StatusGo;
namespace Utils = Status::StatusGo::Utils;
namespace Status::Onboarding
{
AccountsService::AccountsService()
: m_isFirstTimeAccountLogin(false)
{
@ -49,32 +49,32 @@ bool AccountsService::init(const fs::path& statusgoDataDir)
for(const auto &genAddressObj : response.result)
{
auto gAcc = GeneratedAccountDto::toGeneratedAccountDto(genAddressObj.toObject());
auto gAcc = GeneratedMultiAccount::toGeneratedMultiAccount(genAddressObj.toObject());
gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey);
m_generatedAccounts.push_back(std::move(gAcc));
}
return true;
}
std::vector<AccountDto> AccountsService::openAndListAccounts()
std::vector<MultiAccount> AccountsService::openAndListAccounts()
{
auto response = StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str());
if(response.containsError())
{
qWarning() << response.error.message;
return std::vector<AccountDto>();
return std::vector<MultiAccount>();
}
const auto multiAccounts = response.result;
std::vector<AccountDto> result;
std::vector<MultiAccount> result;
for(const auto &value : multiAccounts)
{
result.push_back(AccountDto::toAccountDto(value.toObject()));
result.push_back(MultiAccount::toMultiAccount(value.toObject()));
}
return result;
}
const std::vector<GeneratedAccountDto>& AccountsService::generatedAccounts() const
const std::vector<GeneratedMultiAccount>& AccountsService::generatedAccounts() const
{
return m_generatedAccounts;
}
@ -91,7 +91,7 @@ bool AccountsService::setupAccountAndLogin(const QString &accountId, const QStri
QJsonObject settings(getAccountSettings(accountId, installationId, displayName));
QJsonObject nodeConfig(getDefaultNodeConfig(installationId));
QString hashedPassword(Utils::hashString(password));
auto hashedPassword(Utils::hashPassword(password));
// This initialize the DB if first time running. Required for storing accounts
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
@ -105,12 +105,12 @@ bool AccountsService::setupAccountAndLogin(const QString &accountId, const QStri
return getLoggedInAccount().isValid();
}
const AccountDto& AccountsService::getLoggedInAccount() const
const MultiAccount& AccountsService::getLoggedInAccount() const
{
return m_loggedInAccount;
}
const GeneratedAccountDto& AccountsService::getImportedAccount() const
const GeneratedMultiAccount& AccountsService::getImportedAccount() const
{
return m_importedAccount;
}
@ -127,7 +127,7 @@ bool AccountsService::setKeyStoreDir(const QString &key)
return !response.containsError();
}
QString AccountsService::login(AccountDto account, const QString& password)
QString AccountsService::login(MultiAccount account, const QString& password)
{
// This is a requirement. Make it more explicit into the status go module
if(!setKeyStoreDir(account.keyUid))
@ -137,7 +137,7 @@ QString AccountsService::login(AccountDto account, const QString& password)
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
return QString("Failed to open accounts before logging in");
QString hashedPassword(Utils::hashString(password));
auto hashedPassword(Utils::hashPassword(password));
QString thumbnailImage;
QString largeImage;
@ -157,8 +157,8 @@ QString AccountsService::login(AccountDto account, const QString& password)
void AccountsService::clear()
{
m_generatedAccounts.clear();
m_loggedInAccount = AccountDto();
m_importedAccount = GeneratedAccountDto();
m_loggedInAccount = MultiAccount();
m_importedAccount = GeneratedMultiAccount();
m_isFirstTimeAccountLogin = false;
}
@ -174,15 +174,15 @@ QString AccountsService::generateAlias(const QString& publicKey)
return response.result;
}
void AccountsService::deleteMultiAccount(const AccountDto &account)
void AccountsService::deleteMultiAccount(const MultiAccount &account)
{
StatusGo::Accounts::deleteMultiaccount(account.keyUid, m_keyStoreDir);
}
DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
const QVector<QString>& paths)
DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId, const StatusGo::HashedPassword& password,
const std::vector<Accounts::DerivationPath> &paths)
{
auto response = StatusGo::Accounts::storeDerivedAccounts(accountId, hashedPassword, paths);
auto response = StatusGo::Accounts::storeDerivedAccounts(accountId, password, paths);
if(response.containsError())
{
qWarning() << response.error.message;
@ -191,31 +191,31 @@ DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId,
return DerivedAccounts::toDerivedAccounts(response.result);
}
StoredAccountDto AccountsService::storeAccount(const QString& accountId, const QString& hashedPassword)
StoredMultiAccount AccountsService::storeAccount(const QString& accountId, const StatusGo::HashedPassword& password)
{
auto response = StatusGo::Accounts::storeAccount(accountId, hashedPassword);
auto response = StatusGo::Accounts::storeAccount(accountId, password);
if(response.containsError())
{
qWarning() << response.error.message;
return StoredAccountDto();
return StoredMultiAccount();
}
return toStoredAccountDto(response.result);
return toStoredMultiAccount(response.result);
}
AccountDto AccountsService::saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
MultiAccount AccountsService::saveAccountAndLogin(const StatusGo::HashedPassword& password, const QJsonObject& account,
const QJsonArray& subaccounts, const QJsonObject& settings,
const QJsonObject& config)
{
if(!StatusGo::Accounts::saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config)) {
if(!StatusGo::Accounts::saveAccountAndLogin(password, account, subaccounts, settings, config)) {
qWarning() << "Failed saving acccount" << account.value("name");
return AccountDto();
return MultiAccount();
}
m_isFirstTimeAccountLogin = true;
return AccountDto::toAccountDto(account);
return MultiAccount::toMultiAccount(account);
}
QJsonObject AccountsService::prepareAccountJsonObject(const GeneratedAccountDto& account, const QString &displayName) const
QJsonObject AccountsService::prepareAccountJsonObject(const GeneratedMultiAccount& account, const QString &displayName) const
{
return QJsonObject{{"name", displayName.isEmpty() ? account.alias : displayName},
{"address", account.address},
@ -225,7 +225,7 @@ QJsonObject AccountsService::prepareAccountJsonObject(const GeneratedAccountDto&
QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId, const QString &displayName) const
{
for(const GeneratedAccountDto &acc : m_generatedAccounts)
for(const GeneratedMultiAccount &acc : m_generatedAccounts)
{
if(acc.id == accountId)
{
@ -245,7 +245,7 @@ QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId
return QJsonObject();
}
QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDto& account, const QString &displayName) const
QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedMultiAccount& account, const QString &displayName) const
{
return {
QJsonObject{
@ -253,7 +253,7 @@ QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDt
{"address", account.derivedAccounts.defaultWallet.address},
{"color", "#4360df"},
{"wallet", true},
{"path", Constants::General::PathDefaultWallet},
{"path", Constants::General::PathDefaultWallet.get()},
{"name", "Status account"},
{"derived-from", account.address}
},
@ -261,7 +261,7 @@ QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDt
{"public-key", account.derivedAccounts.whisper.publicKey},
{"address", account.derivedAccounts.whisper.address},
{"name", displayName.isEmpty() ? account.alias : displayName},
{"path", Constants::General::PathWhisper},
{"path", Constants::General::PathWhisper.get()},
{"chat", true},
{"derived-from", ""}
}
@ -271,7 +271,7 @@ QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDt
QJsonArray AccountsService::getSubaccountDataForAccountId(const QString& accountId, const QString &displayName) const
{
// "All these for loops with a nested if cry for a std::find_if :)"
for(const GeneratedAccountDto &acc : m_generatedAccounts)
for(const GeneratedMultiAccount &acc : m_generatedAccounts)
{
if(acc.id == accountId)
{
@ -302,7 +302,7 @@ QString AccountsService::generateSigningPhrase(int count) const
return words.join(" ");
}
QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedAccountDto& account,
QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedMultiAccount& account,
const QString& installationId,
const QString& displayName) const
{
@ -348,7 +348,7 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedAcc
QJsonObject AccountsService::getAccountSettings(const QString& accountId, const QString& installationId, const QString &displayName) const
{
for(const GeneratedAccountDto &acc : m_generatedAccounts)
for(const GeneratedMultiAccount &acc : m_generatedAccounts)
if(acc.id == accountId)
{

View File

@ -1,5 +1,7 @@
#pragma once
#include <StatusGo/Types.h>
#include "AccountsServiceInterface.h"
namespace Status::Onboarding
@ -25,18 +27,18 @@ public:
bool init(const fs::path& statusgoDataDir) override;
/// \see ServiceInterface
[[nodiscard]] std::vector<AccountDto> openAndListAccounts() override;
[[nodiscard]] std::vector<MultiAccount> openAndListAccounts() override;
/// \see ServiceInterface
[[nodiscard]] const std::vector<GeneratedAccountDto>& generatedAccounts() const override;
[[nodiscard]] const std::vector<GeneratedMultiAccount>& generatedAccounts() const override;
/// \see ServiceInterface
bool setupAccountAndLogin(const QString& accountId, const QString& password, const QString& displayName) override;
/// \see ServiceInterface
[[nodiscard]] const AccountDto& getLoggedInAccount() const override;
[[nodiscard]] const MultiAccount& getLoggedInAccount() const override;
[[nodiscard]] const GeneratedAccountDto& getImportedAccount() const override;
[[nodiscard]] const GeneratedMultiAccount& getImportedAccount() const override;
/// \see ServiceInterface
[[nodiscard]] bool isFirstTimeAccountLogin() const override;
@ -44,34 +46,34 @@ public:
/// \see ServiceInterface
bool setKeyStoreDir(const QString &key) override;
QString login(AccountDto account, const QString& password) override;
QString login(MultiAccount account, const QString& password) override;
void clear() override;
QString generateAlias(const QString& publicKey) override;
void deleteMultiAccount(const AccountDto &account) override;
void deleteMultiAccount(const MultiAccount &account) override;
private:
QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
QJsonObject prepareAccountJsonObject(const GeneratedMultiAccount& account, const QString& displayName) const;
DerivedAccounts storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
const QVector<QString>& paths);
StoredAccountDto storeAccount(const QString& accountId, const QString& hashedPassword);
DerivedAccounts storeDerivedAccounts(const QString& accountId, const StatusGo::HashedPassword& password,
const std::vector<Accounts::DerivationPath>& paths);
StoredMultiAccount storeAccount(const QString& accountId, const StatusGo::HashedPassword& password);
AccountDto saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
MultiAccount saveAccountAndLogin(const StatusGo::HashedPassword& password, const QJsonObject& account,
const QJsonArray& subaccounts, const QJsonObject& settings,
const QJsonObject& config);
QJsonObject getAccountDataForAccountId(const QString& accountId, const QString& displayName) const;
QJsonArray prepareSubaccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
QJsonArray prepareSubaccountJsonObject(const GeneratedMultiAccount& account, const QString& displayName) const;
QJsonArray getSubaccountDataForAccountId(const QString& accountId, const QString& displayName) const;
QString generateSigningPhrase(int count) const;
QJsonObject prepareAccountSettingsJsonObject(const GeneratedAccountDto& account,
QJsonObject prepareAccountSettingsJsonObject(const GeneratedMultiAccount& account,
const QString& installationId,
const QString& displayName) const;
@ -80,14 +82,14 @@ private:
QJsonObject getDefaultNodeConfig(const QString& installationId) const;
private:
std::vector<GeneratedAccountDto> m_generatedAccounts;
std::vector<GeneratedMultiAccount> m_generatedAccounts;
fs::path m_statusgoDataDir;
fs::path m_keyStoreDir;
bool m_isFirstTimeAccountLogin;
// TODO: don't see the need for this state here
AccountDto m_loggedInAccount;
GeneratedAccountDto m_importedAccount;
MultiAccount m_loggedInAccount;
GeneratedMultiAccount m_importedAccount;
// Here for now. Extract them if used by other services
static constexpr auto m_keyStoreDirName = "keystore";

View File

@ -1,7 +1,7 @@
#pragma once
#include "AccountDto.h"
#include "GeneratedAccountDto.h"
#include "MultiAccount.h"
#include "GeneratedMultiAccount.h"
#include <filesystem>
@ -20,18 +20,18 @@ public:
virtual bool init(const fs::path& statusgoDataDir) = 0;
/// opens database and returns accounts list.
[[nodiscard]] virtual std::vector<AccountDto> openAndListAccounts() = 0;
[[nodiscard]] virtual std::vector<MultiAccount> openAndListAccounts() = 0;
/// Retrieve cached accounts generated in \c init
[[nodiscard]] virtual const std::vector<GeneratedAccountDto>& generatedAccounts() const = 0;
[[nodiscard]] virtual const std::vector<GeneratedMultiAccount>& generatedAccounts() const = 0;
/// Configure an generated account. \a accountID must be sourced from \c generatedAccounts
virtual bool setupAccountAndLogin(const QString& accountID, const QString& password, const QString& displayName) = 0;
/// Account that is currently logged-in
[[nodiscard]] virtual const AccountDto& getLoggedInAccount() const = 0;
[[nodiscard]] virtual const MultiAccount& getLoggedInAccount() const = 0;
[[nodiscard]] virtual const GeneratedAccountDto& getImportedAccount() const = 0;
[[nodiscard]] virtual const GeneratedMultiAccount& getImportedAccount() const = 0;
/// Check if the login was never done in the current \c data directory
[[nodiscard]] virtual bool isFirstTimeAccountLogin() const = 0;
@ -39,13 +39,13 @@ public:
/// Set and initializes the keystore directory. \see StatusGo::General::initKeystore
virtual bool setKeyStoreDir(const QString &key) = 0;
virtual QString login(AccountDto account, const QString& password) = 0;
virtual QString login(MultiAccount account, const QString& password) = 0;
virtual void clear() = 0;
virtual QString generateAlias(const QString& publicKey) = 0;
virtual void deleteMultiAccount(const AccountDto &account) = 0;
virtual void deleteMultiAccount(const MultiAccount &account) = 0;
};
using AccountsServiceInterfacePtr = std::shared_ptr<AccountsServiceInterface>;

View File

@ -50,19 +50,19 @@ struct DerivedAccounts
for(const auto &derivationPath : jsonObj.keys())
{
auto derivedObj = jsonObj.value(derivationPath).toObject();
if(derivationPath == Constants::General::PathWhisper)
if(derivationPath == Constants::General::PathWhisper.get())
{
result.whisper = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
}
else if(derivationPath == Constants::General::PathWalletRoot)
else if(derivationPath == Constants::General::PathWalletRoot.get())
{
result.walletRoot = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
}
else if(derivationPath == Constants::General::PathDefaultWallet)
else if(derivationPath == Constants::General::PathDefaultWallet.get())
{
result.defaultWallet = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
}
else if(derivationPath == Constants::General::PathEIP1581)
else if(derivationPath == Constants::General::PathEIP1581.get())
{
result.eip1581 = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
}
@ -72,28 +72,28 @@ struct DerivedAccounts
}
};
struct StoredAccountDto
struct StoredMultiAccount
{
QString publicKey;
QString address;
};
static StoredAccountDto toStoredAccountDto(const QJsonObject& jsonObj)
static StoredMultiAccount toStoredMultiAccount(const QJsonObject& jsonObj)
{
auto result = StoredAccountDto();
auto result = StoredMultiAccount();
try {
result.address = Json::getMandatoryProp(jsonObj, "address")->toString();
result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString();
} catch (std::exception e) {
qWarning() << QString("Mapping StoredAccountDto failed: %1").arg(e.what());
qWarning() << QString("Mapping StoredMultiAccount failed: %1").arg(e.what());
}
return result;
}
struct GeneratedAccountDto
struct GeneratedMultiAccount
{
QString id;
QString publicKey;
@ -110,9 +110,9 @@ struct GeneratedAccountDto
return !(id.isEmpty() || publicKey.isEmpty() || address.isEmpty() || keyUid.isEmpty());
}
static GeneratedAccountDto toGeneratedAccountDto(const QJsonObject& jsonObj)
static GeneratedMultiAccount toGeneratedMultiAccount(const QJsonObject& jsonObj)
{
auto result = GeneratedAccountDto();
auto result = GeneratedMultiAccount();
try
{
@ -130,7 +130,7 @@ struct GeneratedAccountDto
}
catch (std::exception e)
{
qWarning() << QString("Mapping GeneratedAccountDto failed: %1").arg(e.what());
qWarning() << QString("Mapping GeneratedMultiAccount failed: %1").arg(e.what());
}
return result;

View File

@ -4,14 +4,18 @@
#include "Common/SigningPhrases.h"
#include "Common/Json.h"
#include <StatusGo/Accounts/accounts_types.h>
#include <QtCore>
namespace Accounts = Status::StatusGo::Accounts;
// TODO: Move to StatusGo library
namespace Status::Onboarding
{
// TODO: refactor it to MultiAccount
struct AccountDto
/// \note equivalent of status-go's multiaccounts.Account@multiaccounts/database.go
struct MultiAccount
{
QString name;
long timestamp;
@ -20,16 +24,16 @@ struct AccountDto
// TODO images
// TODO colorHash
// TODO colorId
QString address;
Accounts::EOAddress address;
bool isValid() const
{
return !(name.isEmpty() || keyUid.isEmpty());
}
static AccountDto toAccountDto(const QJsonObject& jsonObj)
static MultiAccount toMultiAccount(const QJsonObject& jsonObj)
{
auto result = AccountDto();
auto result = MultiAccount();
try
{
@ -43,13 +47,13 @@ struct AccountDto
}
result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString();
result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString();
result.address = Json::getProp(jsonObj, "address")->toString();
result.address = Accounts::EOAddress(Json::getProp(jsonObj, "address")->toString());
/// TODO: investigate unhandled `photo-path` value
}
catch (std::exception e)
{
qWarning() << QObject::tr("Mapping AccountDto failed: %1").arg(e.what());
qWarning() << QString("Mapping MultiAccount failed: %1").arg(e.what());
}
return result;

View File

@ -6,8 +6,8 @@ target_sources(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/UserAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/UserAccountsModel.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountDto.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/GeneratedAccountDto.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/MultiAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/GeneratedMultiAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountsService.h
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountsServiceInterface.h

View File

@ -1,8 +1,12 @@
#pragma once
#include <StatusGo/Accounts/accounts_types.h>
#include <QtCore>
#include <QStringLiteral>
namespace Accounts = Status::StatusGo::Accounts;
namespace Status::Constants
{
@ -34,15 +38,15 @@ namespace General
inline const auto ZeroAddress = u"0x0000000000000000000000000000000000000000"_qs;
inline const auto PathWalletRoot = u"m/44'/60'/0'/0"_qs;
inline const Accounts::DerivationPath PathWalletRoot{u"m/44'/60'/0'/0"_qs};
// EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived
inline const auto PathEIP1581 = u"m/43'/60'/1581'"_qs;
inline const Accounts::DerivationPath PathEIP1581{u"m/43'/60'/1581'"_qs};
// BIP44-0 Wallet key, the default wallet key
inline const auto PathDefaultWallet = PathWalletRoot + u"/0"_qs;
inline const Accounts::DerivationPath PathDefaultWallet{PathWalletRoot.get() + u"/0"_qs};
// EIP1581 Chat Key 0, the default whisper key
inline const auto PathWhisper = PathEIP1581 + u"/0'/0"_qs;
inline const Accounts::DerivationPath PathWhisper{PathEIP1581.get() + u"/0'/0"_qs};
inline const QVector<QString> AccountDefaultPaths {PathWalletRoot, PathEIP1581, PathWhisper, PathDefaultWallet};
inline const std::vector<Accounts::DerivationPath> AccountDefaultPaths {PathWalletRoot, PathEIP1581, PathWhisper, PathDefaultWallet};
}
}

View File

@ -3,7 +3,7 @@
#include "UserAccountsModel.h"
#include "Accounts/AccountsServiceInterface.h"
#include "Accounts/AccountDto.h"
#include "Accounts/MultiAccount.h"
#include <QtQmlIntegration>

View File

@ -17,7 +17,7 @@ OnboardingController::OnboardingController(AccountsServiceInterfacePtr accountsS
{ // Init accounts
std::vector<std::shared_ptr<UserAccount>> accounts;
for(auto &account : getOpenedAccounts()) {
accounts.push_back(std::make_shared<UserAccount>(std::make_unique<AccountDto>(std::move(account))));
accounts.push_back(std::make_shared<UserAccount>(std::make_unique<MultiAccount>(std::move(account))));
}
m_accounts = std::make_shared<UserAccountsModel>(std::move(accounts));
}
@ -38,7 +38,7 @@ void OnboardingController::onLogin(const QString& error)
emit accountLoginError(error);
}
std::vector<AccountDto> OnboardingController::getOpenedAccounts() const
std::vector<MultiAccount> OnboardingController::getOpenedAccounts() const
{
return m_accountsService->openAndListAccounts();
}

View File

@ -2,7 +2,7 @@
#include "UserAccountsModel.h"
#include "Accounts/AccountDto.h"
#include "Accounts/MultiAccount.h"
#include <QQmlEngine>
#include <QtQmlIntegration>
@ -38,7 +38,7 @@ public:
~OnboardingController();
/// Retrieve available accounts
std::vector<AccountDto> getOpenedAccounts() const;
std::vector<MultiAccount> getOpenedAccounts() const;
/// Login user account
/// TODO: \a user should be of type \c UserAccount but this doesn't work with Qt6 CMake API. Investigate and fix later on

View File

@ -1,12 +1,11 @@
#include "UserAccount.h"
#include "Accounts/AccountDto.h"
#include "Accounts/MultiAccount.h"
namespace Status::Onboarding
{
UserAccount::UserAccount(std::unique_ptr<AccountDto> data)
UserAccount::UserAccount(std::unique_ptr<MultiAccount> data)
: QObject()
, m_data(std::move(data))
{
@ -18,12 +17,12 @@ const QString &UserAccount::name() const
return m_data->name;
}
const AccountDto &UserAccount::accountData() const
const MultiAccount &UserAccount::accountData() const
{
return *m_data;
}
void UserAccount::updateAccountData(const AccountDto& newData)
void UserAccount::updateAccountData(const MultiAccount& newData)
{
std::vector<std::function<void()>> notifyUpdates;

View File

@ -5,7 +5,7 @@
namespace Status::Onboarding
{
class AccountDto;
class MultiAccount;
/*!
* \brief Represents a user account in Onboarding Presentation Layer
@ -21,18 +21,18 @@ class UserAccount: public QObject
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
explicit UserAccount(std::unique_ptr<AccountDto> data);
explicit UserAccount(std::unique_ptr<MultiAccount> data);
const QString &name() const;
const AccountDto& accountData() const;
void updateAccountData(const AccountDto& newData);
const MultiAccount& accountData() const;
void updateAccountData(const MultiAccount& newData);
signals:
void nameChanged();
private:
std::unique_ptr<AccountDto> m_data;
std::unique_ptr<MultiAccount> m_data;
};
}

View File

@ -41,12 +41,8 @@ target_link_libraries(${PROJECT_NAME}
Status::OnboardingTestHelpers
Status::Onboarding
# TODO tmp
Status::StatusGoQt
)
include(GoogleTest)
gtest_add_tests(
TARGET ${PROJECT_NAME}

View File

@ -110,7 +110,7 @@ void ScopedTestAccount::logOut()
throw std::runtime_error("ScopedTestAccount - failed logging out");
}
Accounts::MultiAccount ScopedTestAccount::firstChatAccount()
Accounts::ChatOrWalletAccount ScopedTestAccount::firstChatAccount()
{
auto accounts = Accounts::getAccounts();
auto chatIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) {
@ -121,7 +121,7 @@ Accounts::MultiAccount ScopedTestAccount::firstChatAccount()
return *chatIt;
}
Accounts::MultiAccount ScopedTestAccount::firstWalletAccount()
Accounts::ChatOrWalletAccount ScopedTestAccount::firstWalletAccount()
{
auto accounts = Accounts::getAccounts();
auto walletIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) {

View File

@ -36,8 +36,8 @@ public:
void processMessages(size_t millis, std::function<bool()> shouldWaitUntilTimeout);
void logOut();
static Accounts::MultiAccount firstChatAccount();
static Accounts::MultiAccount firstWalletAccount();
static Accounts::ChatOrWalletAccount firstChatAccount();
static Accounts::ChatOrWalletAccount firstWalletAccount();
QString password() const { return m_accountPassword; };

View File

@ -21,17 +21,17 @@ public:
virtual ~AccountsServiceMock() override {};
MOCK_METHOD(bool, init, (const fs::path&), (override));
MOCK_METHOD(std::vector<Onboarding::AccountDto>, openAndListAccounts, (), (override));
MOCK_METHOD(const std::vector<Onboarding::GeneratedAccountDto>&, generatedAccounts, (), (const, override));
MOCK_METHOD(std::vector<Onboarding::MultiAccount>, openAndListAccounts, (), (override));
MOCK_METHOD(const std::vector<Onboarding::GeneratedMultiAccount>&, generatedAccounts, (), (const, override));
MOCK_METHOD(bool, setupAccountAndLogin, (const QString&, const QString&, const QString&), (override));
MOCK_METHOD(const Onboarding::AccountDto&, getLoggedInAccount, (), (const, override));
MOCK_METHOD(const Onboarding::GeneratedAccountDto&, getImportedAccount, (), (const, override));
MOCK_METHOD(const Onboarding::MultiAccount&, getLoggedInAccount, (), (const, override));
MOCK_METHOD(const Onboarding::GeneratedMultiAccount&, getImportedAccount, (), (const, override));
MOCK_METHOD(bool, isFirstTimeAccountLogin, (), (const, override));
MOCK_METHOD(bool, setKeyStoreDir, (const QString&), (override));
MOCK_METHOD(QString, login, (Onboarding::AccountDto, const QString&), (override));
MOCK_METHOD(QString, login, (Onboarding::MultiAccount, const QString&), (override));
MOCK_METHOD(void, clear, (), (override));
MOCK_METHOD(QString, generateAlias, (const QString&), (override));
MOCK_METHOD(void, deleteMultiAccount, (const Onboarding::AccountDto&), (override));
MOCK_METHOD(void, deleteMultiAccount, (const Onboarding::MultiAccount&), (override));
};
}

View File

@ -18,7 +18,6 @@ add_library(${PROJECT_NAME} SHARED)
# Use by linker only
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
# TODO: consider adding a private header for parsing and keep json dependency away!?
target_link_libraries(${PROJECT_NAME}
PUBLIC
Status::Helpers
@ -76,10 +75,11 @@ target_sources(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/accounts_types.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/MultiAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/MultiAccount.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.cpp

View File

@ -9,7 +9,7 @@ const int MNEMONIC_PHRASE_LENGTH = 12;
namespace Status::StatusGo::Accounts {
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths)
RpcResponse<QJsonArray> generateAddresses(const std::vector<Accounts::DerivationPath>& paths)
{
QJsonObject payload{
{"n", NUMBER_OF_ADDRESSES_TO_GENERATE},
@ -64,13 +64,12 @@ RpcResponse<QString> generateAlias(const QString& publicKey)
}
}
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& id, const QString& hashedPassword,
const QVector<QString>& paths)
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& id, const HashedPassword& password, const std::vector<Accounts::DerivationPath>& paths)
{
QJsonObject payload{
{"accountID", id},
{"paths", Utils::toJsonArray(paths)},
{"password", hashedPassword}
{"password", password.get()}
};
try
@ -101,11 +100,11 @@ RpcResponse<QJsonObject> storeDerivedAccounts(const QString& id, const QString&
}
}
RpcResponse<QJsonObject> storeAccount(const QString& id, const QString& hashedPassword)
RpcResponse<QJsonObject> storeAccount(const QString& id, const HashedPassword& password)
{
QJsonObject payload{
{"accountID", id},
{"password", hashedPassword}
{"password", password.get()}
};
try
@ -136,14 +135,14 @@ RpcResponse<QJsonObject> storeAccount(const QString& id, const QString& hashedPa
}
}
bool saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
bool saveAccountAndLogin(const HashedPassword& password, const QJsonObject& account,
const QJsonArray& subaccounts, const QJsonObject& settings,
const QJsonObject& nodeConfig)
{
try
{
auto result = SaveAccountAndLogin(Utils::jsonToByteArray(account).data(),
hashedPassword.toUtf8().data(),
password.get().toUtf8().data(),
Utils::jsonToByteArray(settings).data(),
Utils::jsonToByteArray(nodeConfig).data(),
Utils::jsonToByteArray(subaccounts).data());
@ -193,7 +192,7 @@ RpcResponse<QJsonArray> openAccounts(const char* dataDirPath)
}
}
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const HashedPassword& password,
const QString& thumbnail, const QString& large)
{
QJsonObject payload{
@ -210,7 +209,7 @@ RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const
try
{
auto payloadData = Utils::jsonToByteArray(std::move(payload));
auto result = Login(payloadData.data(), hashedPassword.toUtf8().data());
auto result = Login(payloadData.data(), password.get().toUtf8().data());
QJsonObject jsonResult;
if(!Utils::checkReceivedResponse(result, jsonResult))
{
@ -234,7 +233,7 @@ RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const
}
}
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const HashedPassword& password,
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig)
{
QJsonObject payload{
@ -252,7 +251,7 @@ RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& key
{
auto payloadData = Utils::jsonToByteArray(std::move(payload));
auto nodeConfigData = Utils::jsonToByteArray(nodeConfig);
auto result = LoginWithConfig(payloadData.data(), hashedPassword.toUtf8().data(), nodeConfigData.data());
auto result = LoginWithConfig(payloadData.data(), password.get().toUtf8().data(), nodeConfigData.data());
QJsonObject jsonResult;
if(!Utils::checkReceivedResponse(result, jsonResult))
{

View File

@ -1,31 +1,31 @@
#pragma once
#include "Types.h"
#include "accounts_types.h"
#include <QtCore>
namespace Status::StatusGo::Accounts
{
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
RpcResponse<QJsonArray> generateAddresses(const std::vector<Accounts::DerivationPath> &paths);
RpcResponse<QString> generateAlias(const QString& publicKey);
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
const QVector<QString>& paths);
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const HashedPassword& password,
const std::vector<Accounts::DerivationPath>& paths);
RpcResponse<QJsonObject> storeAccount(const QString& id, const QString& hashedPassword);
RpcResponse<QJsonObject> storeAccount(const QString& id, const HashedPassword& password);
bool saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
bool saveAccountAndLogin(const StatusGo::HashedPassword& password, const QJsonObject& account,
const QJsonArray& subaccounts, const QJsonObject& settings,
const QJsonObject& nodeConfig);
/// opens database and returns accounts list.
RpcResponse<QJsonArray> openAccounts(const char* dataDirPath);
/// TODO harmonise password parameters (hashed or plain)?
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const HashedPassword& password,
const QString& thumbnail, const QString& large);
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const HashedPassword& password,
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig);
RpcResponse<QJsonObject> logout();
}

View File

@ -14,7 +14,7 @@ using json = nlohmann::json;
namespace Status::StatusGo::Accounts
{
Accounts::MultiAccounts getAccounts() {
Accounts::ChatOrWalletAccounts getAccounts() {
// or even nicer with a raw string literal
json inputJson = {
{"jsonrpc", "2.0"},
@ -29,10 +29,10 @@ Accounts::MultiAccounts getAccounts() {
return resultJson.get<CallPrivateRpcResponse>().result;
}
void generateAccountWithDerivedPath(const QString &hashedPassword, const QString &name, const QColor &color, const QString &emoji,
const QString &path, const QString &derivedFrom)
void generateAccountWithDerivedPath(const HashedPassword &password, const QString &name, const QColor &color, const QString &emoji,
const DerivationPath &path, const EOAddress &derivedFrom)
{
std::vector<json> params = {hashedPassword, name, color, emoji, path, derivedFrom};
std::vector<json> params = {password, name, color, emoji, path, derivedFrom};
json inputJson = {
{"jsonrpc", "2.0"},
{"method", "accounts_generateAccountWithDerivedPath"},
@ -44,10 +44,10 @@ void generateAccountWithDerivedPath(const QString &hashedPassword, const QString
checkPrivateRpcCallResultAndReportError(resultJson);
}
void addAccountWithMnemonicAndPath(const QString &mnemonic, const QString &hashedPassword, const QString &name,
const QColor &color, const QString &emoji, const QString &path)
void addAccountWithMnemonicAndPath(const QString &mnemonic, const HashedPassword &password, const QString &name,
const QColor &color, const QString &emoji, const DerivationPath &path)
{
std::vector<json> params = {mnemonic, hashedPassword, name, color, emoji, path};
std::vector<json> params = {mnemonic, password, name, color, emoji, path};
json inputJson = {
{"jsonrpc", "2.0"},
{"method", "accounts_addAccountWithMnemonicAndPath"},
@ -59,7 +59,7 @@ void addAccountWithMnemonicAndPath(const QString &mnemonic, const QString &hashe
checkPrivateRpcCallResultAndReportError(resultJson);
}
void addAccountWatch(const QString &address, const QString &name, const QColor &color, const QString &emoji)
void addAccountWatch(const EOAddress &address, const QString &name, const QColor &color, const QString &emoji)
{
std::vector<json> params = {address, name, color, emoji};
json inputJson = {
@ -73,7 +73,7 @@ void addAccountWatch(const QString &address, const QString &name, const QColor &
checkPrivateRpcCallResultAndReportError(resultJson);
}
void deleteAccount(const QString &address)
void deleteAccount(const EOAddress &address)
{
std::vector<json> params = {address};
json inputJson = {

View File

@ -1,7 +1,9 @@
#pragma once
#include "Types.h"
#include "Accounts/MultiAccount.h"
#include "Accounts/ChatOrWalletAccount.h"
#include "Accounts/accounts_types.h"
#include <vector>
#include <filesystem>
@ -16,34 +18,34 @@ namespace Status::StatusGo::Accounts
/// \brief Retrieve all available accounts Wallet and Chat
/// \note status-go returns accounts in \c CallPrivateRpcResponse.result
/// \throws \c CallPrivateRpcError
Accounts::MultiAccounts getAccounts();
Accounts::ChatOrWalletAccounts getAccounts();
/// \brief Generate a new account
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
/// \see \c getAccounts
/// \throws \c CallPrivateRpcError
void generateAccountWithDerivedPath(const QString &password, const QString &name,
void generateAccountWithDerivedPath(const HashedPassword &password, const QString &name,
const QColor &color, const QString &emoji,
const QString &path, const QString &derivedFrom);
const DerivationPath &path, const Accounts::EOAddress &derivedFrom);
/// \brief Add a new account from an existing mnemonic
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
/// \see \c getAccounts
/// \throws \c CallPrivateRpcError
void addAccountWithMnemonicAndPath(const QString &mnemonic, const QString &hashedPassword, const QString &name,
const QColor &color, const QString &emoji, const QString &path);
void addAccountWithMnemonicAndPath(const QString &mnemonic, const HashedPassword &password, const QString &name,
const QColor &color, const QString &emoji, const DerivationPath &path);
/// \brief Add a watch only account
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
/// \see \c getAccounts
/// \throws \c CallPrivateRpcError
void addAccountWatch(const QString &address, const QString &name, const QColor &color, const QString &emoji);
void addAccountWatch(const EOAddress &address, const QString &name, const QColor &color, const QString &emoji);
/// \brief Delete an existing account
/// \note the underlying status-go api, DeleteAccount@accounts.go, returns `os.Remove(keyFile)`
/// \see \c getAccounts
/// \throws \c CallPrivateRpcError
void deleteAccount(const QString &address);
void deleteAccount(const EOAddress &address);
/// \brief Delete an existing account
/// \note the underlying status-go api, DeleteAccount@accounts.go, returns `os.Remove(keyFile)`

View File

@ -1,8 +1,8 @@
#include "MultiAccount.h"
#include "ChatOrWalletAccount.h"
namespace Status::StatusGo::Accounts {
void to_json(json& j, const MultiAccount& d) {
void to_json(json& j, const ChatOrWalletAccount& d) {
j = {{"address", d.address},
{"chat", d.isChat},
{"clock", d.clock},
@ -21,7 +21,7 @@ void to_json(json& j, const MultiAccount& d) {
j["derived-from"] = d.derivedFrom.value();
}
void from_json(const json& j, MultiAccount& d) {
void from_json(const json& j, ChatOrWalletAccount& d) {
j.at("address").get_to(d.address);
j.at("chat").get_to(d.isChat);
j.at("clock").get_to(d.clock);
@ -40,7 +40,7 @@ void from_json(const json& j, MultiAccount& d) {
if(j.contains(publicKeyKey))
j.at(publicKeyKey).get_to(d.publicKey);
if(d.isWallet && !j.at("derived-from").get<std::string>().empty())
d.derivedFrom = j.at("derived-from").get<QString>();
d.derivedFrom = j.at("derived-from").get<std::optional<EOAddress>>();
}
}

View File

@ -0,0 +1,42 @@
#pragma once
#include "accounts_types.h"
#include <Helpers/conversions.h>
#include <QColor>
#include <nlohmann/json.hpp>
#include <vector>
using json = nlohmann::json;
namespace Status::StatusGo::Accounts {
/// \brief Unique wallet account entity
/// \note equivalent of status-go's accounts.Account@multiaccounts/accounts/database.go
struct ChatOrWalletAccount
{
EOAddress address;
bool isChat = false;
int clock = -1;
QColor color;
std::optional<EOAddress> derivedFrom;
QString emoji;
bool isHidden = false;
QString mixedcaseAddress;
QString name;
DerivationPath path;
QString publicKey;
bool isRemoved = false;
bool isWallet = false;
};
using ChatOrWalletAccounts = std::vector<ChatOrWalletAccount>;
void to_json(json& j, const ChatOrWalletAccount& d);
void from_json(const json& j, ChatOrWalletAccount& d);
}

View File

@ -1,42 +0,0 @@
#pragma once
#include <Helpers/conversions.h>
#include <QColor>
#include <nlohmann/json.hpp>
#include <vector>
using json = nlohmann::json;
namespace Status::StatusGo::Accounts {
// TODO: rename to MixedAccount
// TODO: create custom types or just named types for all. Also fix APIs after this
/*! \brief Unique wallet account entity
*/
struct MultiAccount
{
QString address;
bool isChat = false;
int clock = -1;
QColor color;
std::optional<QString> derivedFrom;
QString emoji;
bool isHidden = false;
QString mixedcaseAddress;
QString name;
QString path;
QString publicKey;
bool isRemoved = false;
bool isWallet = false;
};
using MultiAccounts = std::vector<MultiAccount>;
void to_json(json& j, const MultiAccount& d);
void from_json(const json& j, MultiAccount& d);
}

View File

@ -0,0 +1,16 @@
#include <Helpers/NamedType.h>
#include <nlohmann/json.hpp>
#include <QString>
using json = nlohmann::json;
/// Defines phantom types for strong typing
namespace Status::StatusGo::Accounts {
/// The 20 byte address of an Ethereum account prefixed with 0x
using EOAddress = Helpers::NamedType<QString, struct EOAddressTag>;
using DerivationPath = Helpers::NamedType<QString, struct DerivationPathTag>;
}

View File

@ -1,10 +1,14 @@
#pragma once
#include <Helpers/NamedType.h>
#include <QString>
namespace Status::StatusGo
{
using HashedPassword = Helpers::NamedType<QString, struct HashedPasswordTag>;
// Used in calls where we don't have version and id returned from `status-go`
struct RpcError

View File

@ -7,11 +7,11 @@
namespace Status::StatusGo::Utils
{
QJsonArray toJsonArray(const QVector<QString>& value)
QJsonArray toJsonArray(const std::vector<Accounts::DerivationPath>& value)
{
QJsonArray array;
for(auto& v : value)
array << v;
array << v.get();
return array;
}
@ -20,10 +20,10 @@ const char* statusGoCallPrivateRPC(const char* inputJSON) {
return CallPrivateRPC(const_cast<char*>(inputJSON));
}
QString hashString(const QString &str)
HashedPassword hashPassword(const QString &str)
{
return "0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(),
QCryptographicHash::Keccak_256).toHex());
return HashedPassword("0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(),
QCryptographicHash::Keccak_256).toHex()));
}
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json)

View File

@ -1,6 +1,7 @@
#pragma once
#include "Types.h"
#include "Accounts/accounts_types.h"
#include <QJsonDocument>
#include <QJsonObject>
@ -26,7 +27,7 @@ QByteArray jsonToByteArray(const T& json)
return QJsonDocument(json).toJson(QJsonDocument::Compact);
}
QJsonArray toJsonArray(const QVector<QString>& value);
QJsonArray toJsonArray(const std::vector<Accounts::DerivationPath>& value);
/// Check if json contains a standard status-go error and
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json);
@ -111,6 +112,6 @@ RpcResponse<T> callPrivateRpc(const QByteArray& payload)
}
}
QString hashString(const QString &str);
HashedPassword hashPassword(const QString &str);
}

View File

@ -1,5 +1,7 @@
#pragma once
#include "Accounts/accounts_types.h"
#include <Helpers/conversions.h>
#include <QColor>
@ -8,6 +10,8 @@
#include <vector>
namespace Accounts = Status::StatusGo::Accounts;
using json = nlohmann::json;
namespace Status::StatusGo::Wallet {
@ -19,10 +23,8 @@ namespace Status::StatusGo::Wallet {
*/
struct DerivedAddress
{
// TODO create and Address type represents the 20 byte address of an Ethereum account. See https://pkg.go.dev/github.com/ethereum/go-ethereum/common?utm_source=gopls#Address
QString address;
// TODO: create an Path named type
QString path;
Accounts::EOAddress address;
Accounts::DerivationPath path;
bool hasActivity = false;
bool alreadyCreated = false;
};

View File

@ -3,20 +3,24 @@
#include "Utils.h"
#include "Metadata/api_response.h"
#include "Accounts/accounts_types.h"
#include <libstatus.h>
#include <nlohmann/json.hpp>
#include <iostream>
namespace Accounts = Status::StatusGo::Accounts;
using json = nlohmann::json;
namespace Status::StatusGo::Wallet
{
DerivedAddresses getDerivedAddressesForPath(const QString &hashedPassword, const QString &derivedFrom, const QString &path, int pageSize, int pageNumber)
DerivedAddresses getDerivedAddressesForPath(const HashedPassword &password, const Accounts::EOAddress &derivedFrom, const Accounts::DerivationPath &path, int pageSize, int pageNumber)
{
std::vector<json> params = {hashedPassword, derivedFrom, path, pageSize, pageNumber};
std::vector<json> params = {password, derivedFrom, path, pageSize, pageNumber};
json inputJson = {
{"jsonrpc", "2.0"},
{"method", "wallet_getDerivedAddressesForPath"},

View File

@ -1,8 +1,9 @@
#pragma once
#include "Accounts/MultiAccount.h"
#include "Accounts/ChatOrWalletAccount.h"
#include "Accounts/accounts_types.h"
#include "DerivedAddress.h"
#include "Types.h"
#include <vector>
@ -13,6 +14,6 @@ namespace Status::StatusGo::Wallet
/// \brief Retrieve a list of derived account addresses
/// \see \c generateAccountWithDerivedPath
/// \throws \c CallPrivateRpcError
DerivedAddresses getDerivedAddressesForPath(const QString &password, const QString &derivedFrom, const QString &path, int pageSize, int pageNumber);
DerivedAddresses getDerivedAddressesForPath(const HashedPassword &password, const Accounts::EOAddress &derivedFrom, const Accounts::DerivationPath &path, int pageSize, int pageNumber);
} // namespaces

View File

@ -13,7 +13,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
# Required to suppress "qmllint may not work" warning

View File

@ -10,7 +10,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Containers
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
LayoutSpacer.qml

View File

@ -10,7 +10,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Controls
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusBanner.qml

View File

@ -14,7 +14,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Controls.Navigation
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
ApplicationContentView.qml
ApplicationSection.qml

View File

@ -10,7 +10,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusBaseText.qml

View File

@ -20,7 +20,6 @@ qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core.Theme
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusColors.qml
StatusDarkPalette.qml

View File

@ -12,8 +12,9 @@ qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.TestHelpers
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/TestHelpers

View File

@ -36,14 +36,14 @@ TEST(AccountsAPI, TestGetAccounts)
ASSERT_NE(chatIt, accounts.end());
const auto &chatAccount = *chatIt;
ASSERT_EQ(chatAccount.name, testAccountName);
ASSERT_FALSE(chatAccount.path.isEmpty());
ASSERT_FALSE(chatAccount.path.get().isEmpty());
ASSERT_FALSE(chatAccount.derivedFrom.has_value());
const auto walletIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) { return a.isWallet; });
ASSERT_NE(walletIt, accounts.end());
const auto &walletAccount = *walletIt;
ASSERT_NE(walletAccount.name, testAccountName);
ASSERT_FALSE(walletAccount.path.isEmpty());
ASSERT_FALSE(walletAccount.path.get().isEmpty());
ASSERT_TRUE(walletAccount.derivedFrom.has_value());
}
@ -53,14 +53,14 @@ TEST(Accounts, TestGenerateAccountWithDerivedPath)
constexpr auto testAccountPassword = "password*";
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
auto hashedPassword{Utils::hashString(testAccountPassword)};
auto password{Utils::hashPassword(testAccountPassword)};
const auto newTestAccountName = u"test_generated_new_account-name"_qs;
const auto newTestAccountColor = QColor("fuchsia");
const auto newTestAccountEmoji = u""_qs;
const auto newTestAccountPath = Status::Constants::General::PathWalletRoot;
const auto chatAccount = testAccount.firstChatAccount();
Accounts::generateAccountWithDerivedPath(hashedPassword, newTestAccountName,
Accounts::generateAccountWithDerivedPath(password, newTestAccountName,
newTestAccountColor, newTestAccountEmoji,
newTestAccountPath, chatAccount.address);
const auto updatedAccounts = Accounts::getAccounts();
@ -72,13 +72,13 @@ TEST(Accounts, TestGenerateAccountWithDerivedPath)
});
ASSERT_NE(newAccountIt, updatedAccounts.end());
const auto &newAccount = *newAccountIt;
ASSERT_FALSE(newAccount.address.isEmpty());
ASSERT_FALSE(newAccount.address.get().isEmpty());
ASSERT_FALSE(newAccount.isChat);
ASSERT_FALSE(newAccount.isWallet);
ASSERT_EQ(newAccount.color, newTestAccountColor);
ASSERT_FALSE(newAccount.derivedFrom.has_value());
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.get().toUpper());
ASSERT_EQ(newAccount.path, newTestAccountPath);
ASSERT_FALSE(newAccount.publicKey.isEmpty());
}
@ -91,7 +91,7 @@ TEST(AccountsAPI, TestGenerateAccountWithDerivedPath_WrongPassword)
const auto chatAccount = testAccount.firstChatAccount();
try {
Accounts::generateAccountWithDerivedPath(Utils::hashString("WrongPassword"), u"test_wrong_pass-name"_qs,
Accounts::generateAccountWithDerivedPath(Utils::hashPassword("WrongPassword"), u"test_wrong_pass-name"_qs,
QColor("fuchsia"), "", Status::Constants::General::PathWalletRoot,
chatAccount.address);
FAIL();
@ -111,14 +111,14 @@ TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath)
constexpr auto testAccountPassword = "password*";
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
auto hashedPassword{Utils::hashString(testAccountPassword)};
auto password{Utils::hashPassword(testAccountPassword)};
const auto newTestAccountName = u"test_import_from_mnemonic-name"_qs;
const auto newTestAccountColor = QColor("fuchsia");
const auto newTestAccountEmoji = u""_qs;
const auto newTestAccountPath = Status::Constants::General::PathWalletRoot;
Accounts::addAccountWithMnemonicAndPath("festival october control quarter husband dish throw couch depth stadium cigar whisper",
hashedPassword, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
password, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
newTestAccountPath);
const auto updatedAccounts = Accounts::getAccounts();
ASSERT_EQ(updatedAccounts.size(), 3);
@ -129,13 +129,13 @@ TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath)
});
ASSERT_NE(newAccountIt, updatedAccounts.end());
const auto &newAccount = *newAccountIt;
ASSERT_FALSE(newAccount.address.isEmpty());
ASSERT_FALSE(newAccount.address.get().isEmpty());
ASSERT_FALSE(newAccount.isChat);
ASSERT_FALSE(newAccount.isWallet);
ASSERT_EQ(newAccount.color, newTestAccountColor);
ASSERT_FALSE(newAccount.derivedFrom.has_value());
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.get().toUpper());
ASSERT_EQ(newAccount.path, newTestAccountPath);
ASSERT_FALSE(newAccount.publicKey.isEmpty());
}
@ -147,7 +147,7 @@ TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath_WrongMnemonicWorks)
constexpr auto testAccountPassword = "password*";
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
auto hashedPassword{Utils::hashString(testAccountPassword)};
auto password{Utils::hashPassword(testAccountPassword)};
const auto newTestAccountName = u"test_import_from_wrong_mnemonic-name"_qs;
const auto newTestAccountColor = QColor("fuchsia");
const auto newTestAccountEmoji = u""_qs;
@ -155,7 +155,7 @@ TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath_WrongMnemonicWorks)
// Added an inexistent word. The mnemonic is not checked.
Accounts::addAccountWithMnemonicAndPath("october control quarter husband dish throw couch depth stadium cigar waku",
hashedPassword, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
password, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
newTestAccountPath);
const auto updatedAccounts = Accounts::getAccounts();
@ -168,13 +168,13 @@ TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath_WrongMnemonicWorks)
ASSERT_NE(newAccountIt, updatedAccounts.end());
const auto &newAccount = *newAccountIt;
ASSERT_FALSE(newAccount.address.isEmpty());
ASSERT_FALSE(newAccount.address.get().isEmpty());
ASSERT_FALSE(newAccount.isChat);
ASSERT_FALSE(newAccount.isWallet);
ASSERT_EQ(newAccount.color, newTestAccountColor);
ASSERT_FALSE(newAccount.derivedFrom.has_value());
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.get().toUpper());
ASSERT_EQ(newAccount.path, newTestAccountPath);
ASSERT_FALSE(newAccount.publicKey.isEmpty());
}
@ -189,7 +189,7 @@ TEST(AccountsAPI, TestAddAccountWatch)
const auto newTestAccountColor = QColor("fuchsia");
const auto newTestAccountEmoji = u""_qs;
Accounts::addAccountWatch("0x145b6B821523afFC346774b41ACC7b77A171BbA4", newTestAccountName, newTestAccountColor, newTestAccountEmoji);
Accounts::addAccountWatch(Accounts::EOAddress("0x145b6B821523afFC346774b41ACC7b77A171BbA4"), newTestAccountName, newTestAccountColor, newTestAccountEmoji);
const auto updatedAccounts = Accounts::getAccounts();
ASSERT_EQ(updatedAccounts.size(), 3);
@ -199,14 +199,14 @@ TEST(AccountsAPI, TestAddAccountWatch)
});
ASSERT_NE(newAccountIt, updatedAccounts.end());
const auto &newAccount = *newAccountIt;
ASSERT_FALSE(newAccount.address.isEmpty());
ASSERT_FALSE(newAccount.address.get().isEmpty());
ASSERT_FALSE(newAccount.isChat);
ASSERT_FALSE(newAccount.isWallet);
ASSERT_EQ(newAccount.color, newTestAccountColor);
ASSERT_FALSE(newAccount.derivedFrom.has_value());
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
ASSERT_TRUE(newAccount.path.isEmpty());
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.get().toUpper());
ASSERT_TRUE(newAccount.path.get().isEmpty());
ASSERT_TRUE(newAccount.publicKey.isEmpty());
}
@ -220,7 +220,7 @@ TEST(AccountsAPI, TestDeleteAccount)
const auto newTestAccountColor = QColor("fuchsia");
const auto newTestAccountEmoji = u""_qs;
Accounts::addAccountWatch("0x145b6B821523afFC346774b41ACC7b77A171BbA4", newTestAccountName, newTestAccountColor, newTestAccountEmoji);
Accounts::addAccountWatch(Accounts::EOAddress("0x145b6B821523afFC346774b41ACC7b77A171BbA4"), newTestAccountName, newTestAccountColor, newTestAccountEmoji);
const auto updatedAccounts = Accounts::getAccounts();
ASSERT_EQ(updatedAccounts.size(), 3);

View File

@ -32,11 +32,11 @@ TEST(WalletApi, TestGetDerivedAddressesForPath)
const auto rootAccount = testAccount.onboardingController()->accountsService()->getLoggedInAccount();
ASSERT_EQ(rootAccount.address, walletAccount.derivedFrom.value());
const auto hashedPassword{Utils::hashString(testAccountPassword)};
const auto password{Utils::hashPassword(testAccountPassword)};
const auto testPath = Status::Constants::General::PathWalletRoot;
// chatAccount.address
const auto chatDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, chatAccount.address, testPath, 3, 1);
const auto chatDerivedAddresses = Wallet::getDerivedAddressesForPath(password, chatAccount.address, testPath, 3, 1);
// Check that no change is done
const auto updatedAccounts = Accounts::getAccounts();
ASSERT_EQ(updatedAccounts.size(), 2);
@ -47,14 +47,14 @@ TEST(WalletApi, TestGetDerivedAddressesForPath)
// all hasActivity are false
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.hasActivity; }));
// all address are valid
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.address.isEmpty(); }));
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.address.get().isEmpty(); }));
const auto walletDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, walletAccount.address, testPath, 2, 1);
const auto walletDerivedAddresses = Wallet::getDerivedAddressesForPath(password, walletAccount.address, testPath, 2, 1);
ASSERT_EQ(walletDerivedAddresses.size(), 2);
// all alreadyCreated are false
ASSERT_TRUE(std::none_of(walletDerivedAddresses.begin(), walletDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; }));
const auto rootDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, rootAccount.address, testPath, 4, 1);
const auto rootDerivedAddresses = Wallet::getDerivedAddressesForPath(password, rootAccount.address, testPath, 4, 1);
ASSERT_EQ(rootDerivedAddresses.size(), 4);
ASSERT_EQ(std::count_if(rootDerivedAddresses.begin(), rootDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; }), 1);
const auto &existingAddress = *std::find_if(rootDerivedAddresses.begin(), rootDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; });