From 51917c0a8d0a646680e4910024bf694d4e9de05a Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Thu, 18 Apr 2024 21:57:15 +0200 Subject: [PATCH] feat(fio): Add support for `addbundles` and `remalladdr` actions (#3802) --- src/FIO/Action.cpp | 15 ++++++ src/FIO/Action.h | 31 +++++++++++- src/FIO/TransactionBuilder.cpp | 86 +++++++++++++++++++++++++++++++++ src/FIO/TransactionBuilder.h | 30 ++++++++++++ src/proto/FIO.proto | 24 +++++++++ tests/chains/FIO/TWFIOTests.cpp | 45 +++++++++++++++++ 6 files changed, 230 insertions(+), 1 deletion(-) diff --git a/src/FIO/Action.cpp b/src/FIO/Action.cpp index 5be60a17866..34e1a8455cd 100644 --- a/src/FIO/Action.cpp +++ b/src/FIO/Action.cpp @@ -49,6 +49,13 @@ void PubAddressActionData::serialize(Data& out) const { encodeString(tpid, out); } +void RemoveAllPubAddressActionData::serialize(Data& out) const { + encodeString(fioAddress, out); + encode64LE(fee, out); + EOS::Name(actor).serialize(out); + encodeString(tpid, out); +} + void RegisterFioAddressData::serialize(Data& out) const { encodeString(fioAddress, out); encodeString(ownerPublicKey, out); @@ -81,4 +88,12 @@ void NewFundsRequestData::serialize(Data& out) const { encodeString(tpid, out); } +void AddBundledTransactionsActionData::serialize(Data& out) const { + encodeString(fioAddress, out); + encode64LE(bundledSets, out); + encode64LE(fee, out); + encodeString(tpid, out); + EOS::Name(actor).serialize(out); +} + } // namespace TW::FIO diff --git a/src/FIO/Action.h b/src/FIO/Action.h index 1ebdc3eff91..a5c325d3341 100644 --- a/src/FIO/Action.h +++ b/src/FIO/Action.h @@ -82,7 +82,7 @@ class Action { }; /// A public address action data part. -/// Can be used for `addaddress`, `remaddress` actions. +/// Can be used for `addaddress`, `remaddress`, `remalladdr` (addresses must be empty) actions. /// https://dev.fio.net/reference/add_pub_address /// https://dev.fio.net/reference/remove_pub_address class PubAddressActionData { @@ -100,6 +100,20 @@ class PubAddressActionData { void serialize(Data& out) const; }; +/// RemoveAllPubAddress action data part. +/// https://dev.fio.net/reference/remove_all_pub_address +class RemoveAllPubAddressActionData { +public: + std::string fioAddress; + uint64_t fee; + std::string tpid; + std::string actor; + + RemoveAllPubAddressActionData(const std::string& fioAddress, uint64_t fee, const std::string& tpid, const std::string& actor) : + fioAddress(fioAddress), fee(fee), tpid(tpid), actor(actor) {} + void serialize(Data& out) const; +}; + /// RegisterFioAddress action data part. class RegisterFioAddressData { public: @@ -158,4 +172,19 @@ class NewFundsRequestData { void serialize(Data& out) const; }; +/// AddBundledTransactions action data part. +/// https://dev.fio.net/reference/add_bundled_transactions +class AddBundledTransactionsActionData { +public: + std::string fioAddress; + uint64_t bundledSets; + uint64_t fee; + std::string tpid; + std::string actor; + + AddBundledTransactionsActionData(const std::string& fioAddress, uint64_t bundledSets, uint64_t fee, const std::string& tpid, const std::string& actor) : + fioAddress(fioAddress), bundledSets(bundledSets), fee(fee), tpid(tpid), actor(actor) {} + void serialize(Data& out) const; +}; + } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index bf5ca8a8302..4a890d81705 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -24,9 +24,11 @@ using json = nlohmann::json; static constexpr auto gRegisterFioAddress = "regaddress"; static constexpr auto gAddPubAddress = "addaddress"; static constexpr auto gRemoveAddress = "remaddress"; +static constexpr auto gRemoveAllPubAddresses = "remalladdr"; static constexpr auto gTransferFIOPubkey = "trnsfiopubky"; static constexpr auto gRenewFIOAddress = "renewaddress"; static constexpr auto gNewFundsRequest = "newfundsreq"; +static constexpr auto gAddBundledTransactions = "addbundles"; /// Internal helper ChainParams getChainParams(const Proto::SigningInput& input) { @@ -58,6 +60,10 @@ string TransactionBuilder::actionName(const Proto::SigningInput& input) { return gNewFundsRequest; case Proto::Action::MessageOneofCase::kRemovePubAddressMessage: return gRemoveAddress; + case Proto::Action::MessageOneofCase::kRemoveAllPubAddressesMessage: + return gRemoveAllPubAddresses; + case Proto::Action::MessageOneofCase::kAddBundledTransactionsMessage: + return gAddBundledTransactions; default: return {}; } @@ -111,6 +117,14 @@ string TransactionBuilder::sign(Proto::SigningInput in) { json = TransactionBuilder::createRemovePubAddress(owner, privateKey, action.fio_address(), addresses, getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_remove_all_pub_addresses_message()) { + const auto action = in.action().remove_all_pub_addresses_message(); + json = TransactionBuilder::createRemoveAllPubAddresses(owner, privateKey, + action.fio_address(), getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_bundled_transactions_message()) { + const auto action = in.action().add_bundled_transactions_message(); + json = TransactionBuilder::createAddBundledTransactions(owner, privateKey, action.fio_address(), + action.bundle_sets(), getChainParams(in), action.fee(), in.tpid(), in.expiry()); } return json; } @@ -151,6 +165,28 @@ string TransactionBuilder::createRemovePubAddress(const Address& address, const return signAndBuildTx(chainParams.chainId, serTx, privateKey); } +std::string TransactionBuilder::createRemoveAllPubAddresses(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedRemoveAllAddressesAction(address, fioName, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +std::string TransactionBuilder::createAddBundledTransactions(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedAddBundledTransactions(address, fioName, bundleSets, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + string TransactionBuilder::createTransfer(const Address& address, const PrivateKey& privateKey, const string& payeePublicKey, uint64_t amount, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { @@ -271,6 +307,14 @@ Data TransactionBuilder::buildUnsignedTxBytes(const Proto::SigningInput& in) { } transaction = TransactionBuilder::buildUnsignedPubAddressAction(gRemoveAddress, owner, action.fio_address(), addresses, getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_remove_all_pub_addresses_message()) { + const auto action = in.action().remove_all_pub_addresses_message(); + transaction = TransactionBuilder::buildUnsignedRemoveAllAddressesAction(owner, action.fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_bundled_transactions_message()) { + const auto action = in.action().add_bundled_transactions_message(); + transaction = TransactionBuilder::buildUnsignedAddBundledTransactions(owner, action.fio_address(), action.bundle_sets(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); } Data serTx; @@ -381,4 +425,46 @@ Transaction TransactionBuilder::buildUnsignedRenewFioAddress(const Address& addr return tx; } +Transaction TransactionBuilder::buildUnsignedRemoveAllAddressesAction(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + string actor = Actor::actor(address); + RemoveAllPubAddressActionData actionData(fioName, fee, walletTpId, actor); + Data serData; + actionData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = gRemoveAllPubAddresses; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + +Transaction TransactionBuilder::buildUnsignedAddBundledTransactions(const Address& address, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + string actor = Actor::actor(address); + AddBundledTransactionsActionData actionData(fioName, bundleSets, fee, walletTpId, actor); + Data serData; + actionData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = gAddBundledTransactions; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.h b/src/FIO/TransactionBuilder.h index 735bf38d834..844a58850b1 100644 --- a/src/FIO/TransactionBuilder.h +++ b/src/FIO/TransactionBuilder.h @@ -81,6 +81,18 @@ class TransactionBuilder { const std::vector>& pubAddresses, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Create a signed `remalladdr` transaction, returned as json string (double quote delimited), suitable for remove_all_pub_address RPC call + /// @address The owners' FIO address + /// @privateKey The private key matching the address, needed for signing. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + /// Note: fee is usually 0 for remove_all_pub_address. + static std::string createRemoveAllPubAddresses(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Create a signed TransferTokens transaction, returned as json string (double quote delimited), suitable for transfer_tokens_pub_key RPC call /// @address The owners' FIO address /// @privateKey The private key matching the address, needed for signing. @@ -130,6 +142,18 @@ class TransactionBuilder { const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime, const Data& iv); + /// Create a signed `addbundles` transaction, returned as json string (double quote delimited), suitable for add_bundled_transactions RPC call + /// @address The owners' FIO address + /// @privateKey The private key matching the address, needed for signing. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @bundleSets Number of bundled sets. One set is 100 bundled transactions. + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + static std::string createAddBundledTransactions(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Used internally. Creates signatures and json with transaction. static std::string signAndBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey); @@ -166,6 +190,12 @@ class TransactionBuilder { static Transaction buildUnsignedRenewFioAddress(const Address& address, const std::string& fioName, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedRemoveAllAddressesAction(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedAddBundledTransactions(const Address& address, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); }; } // namespace TW::FIO diff --git a/src/proto/FIO.proto b/src/proto/FIO.proto index 9fdb61b4c70..664c041e5f0 100644 --- a/src/proto/FIO.proto +++ b/src/proto/FIO.proto @@ -77,6 +77,16 @@ message Action { uint64 fee = 3; } + // Action for removing public chain addresses from a FIO name; remove_pub_address + // Note: actor is not needed, computed from private key + message RemoveAllPubAddress { + // The FIO name already registered to the owner. Ex.: "alice@trust" + string fio_address = 1; + + // Max fee to spend, can be obtained using get_fee API. + uint64 fee = 3; + } + // Action for transferring FIO coins; transfer_tokens_pub_key // Note: actor is not needed, computed from private key message Transfer { @@ -122,6 +132,18 @@ message Action { uint64 fee = 5; } + // Action for adding `100 * bundle_sets` bundled transactions to the supplied FIO Handle. When bundles are purchased one or more sets of bundled transactions are added to the existing count. + message AddBundledTransactions { + // The FIO name already registered to the owner. Ex.: "alice@trust" + string fio_address = 1; + + // Number of bundled sets. One set is 100 bundled transactions. + uint64 bundle_sets = 2; + + // Max fee to spend, can be obtained using get_fee API. + uint64 fee = 3; + } + // Payload message oneof message_oneof { RegisterFioAddress register_fio_address_message = 1; @@ -130,6 +152,8 @@ message Action { RenewFioAddress renew_fio_address_message = 4; NewFundsRequest new_funds_request_message = 5; RemovePubAddress remove_pub_address_message = 6; + RemoveAllPubAddress remove_all_pub_addresses_message = 7; + AddBundledTransactions add_bundled_transactions_message = 8; } } diff --git a/tests/chains/FIO/TWFIOTests.cpp b/tests/chains/FIO/TWFIOTests.cpp index 6cf1fd3c7d7..1c4f35285b3 100644 --- a/tests/chains/FIO/TWFIOTests.cpp +++ b/tests/chains/FIO/TWFIOTests.cpp @@ -116,6 +116,28 @@ TEST(TWFIO, RemovePubAddress) { EXPECT_EQ(output.action_name(), "remaddress"); } +TEST(TWFIO, RemoveAllPubAddresses) { + auto privateKey = parse_hex("93083dc4d9e8f613a57e3a862a1fa5d665c5e90141a8428990c945d1c2b56491"); + + Proto::SigningInput input; + input.set_expiry(1713458993); + input.mutable_chain_params()->set_chain_id(string(gChainIdMainnet.begin(), gChainIdMainnet.end())); + input.mutable_chain_params()->set_head_block_number(256432311); + input.mutable_chain_params()->set_ref_block_prefix(2287536876); + input.set_private_key(string(privateKey.begin(), privateKey.end())); + input.set_tpid("trust@fiomembers"); + auto action = input.mutable_action()->mutable_remove_all_pub_addresses_message(); + action->set_fio_address("sergeitrust@wallet"); + action->set_fee(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + // Successfully broadcasted: https://fio.bloks.io/transaction/f2facdebfcba1981377537424a6d7b7e7ebd8222c87ba4d25a480d1b968704b2 + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"314f2166b7d8ec0a59880000000001003056372503a85b00c04dc9c468a4ba01b038b9d6c13372f700000000a8ed3232341273657267656974727573744077616c6c65740000000000000000b038b9d6c13372f71074727573744066696f6d656d6265727300","signatures":["SIG_K1_KXXtpz7NWhzCms7Dj54nSwwtCw6w4zLCyTLxs3tqqgLscrz91cMjcbN4yxcySvZ7t4MER8HPteeJZUnR16uLyDa1gFGzrx"]})", output.json()); + EXPECT_EQ(output.action_name(), "remalladdr"); +} + TEST(TWFIO, Transfer) { Proto::SigningInput input; input.set_expiry(1579790000); @@ -183,4 +205,27 @@ TEST(TWFIO, NewFundsRequest) { EXPECT_EQ(output.action_name(), "newfundsreq"); } +TEST(TWFIO, AddBundledTransactions) { + auto privateKey = parse_hex("93083dc4d9e8f613a57e3a862a1fa5d665c5e90141a8428990c945d1c2b56491"); + + Proto::SigningInput input; + input.set_expiry(1713458594); + input.mutable_chain_params()->set_chain_id(string(gChainIdMainnet.begin(), gChainIdMainnet.end())); + input.mutable_chain_params()->set_head_block_number(256431437); + input.mutable_chain_params()->set_ref_block_prefix(791306279); + input.set_private_key(string(privateKey.begin(), privateKey.end())); + input.set_tpid("trust@fiomembers"); + auto action = input.mutable_action()->mutable_add_bundled_transactions_message(); + action->set_fio_address("sergeitrust@wallet"); + action->set_bundle_sets(1); + action->set_fee(100000000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + // Successfully broadcasted: https://fio.bloks.io/transaction/2c00f2051ca3738c4fe03ceddb82c48fefd9c534d8bb793dc7dce5d12f4f4f9c + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"a24d21664dd527602a2f0000000001003056372503a85b000056314d7d523201b038b9d6c13372f700000000a8ed32323c1273657267656974727573744077616c6c6574010000000000000000e87648170000001074727573744066696f6d656d62657273b038b9d6c13372f700","signatures":["SIG_K1_KjWGZ4Yd48VJcTAgox3HYVQhXeLhpRCgz2WqiF5WHRFSnbHouKxPgLQmymoABHC8EX51G1jU4ocWg2RKU17UYm4L5kTXP6"]})", output.json()); + EXPECT_EQ(output.action_name(), "addbundles"); +} + } // namespace TW::FIO::TWFIOTests