diff --git a/conanfile.py b/conanfile.py index d718e575..23984ddc 100644 --- a/conanfile.py +++ b/conanfile.py @@ -31,7 +31,7 @@ class Clio(ConanFile): 'protobuf/3.21.9', 'grpc/1.50.1', 'openssl/1.1.1v', - 'xrpl/2.4.0', + 'xrpl/2.5.0-b1', 'zlib/1.3.1', 'libbacktrace/cci.20210118' ] diff --git a/src/data/AmendmentCenter.hpp b/src/data/AmendmentCenter.hpp index 1aac5954..127ecd0b 100644 --- a/src/data/AmendmentCenter.hpp +++ b/src/data/AmendmentCenter.hpp @@ -137,6 +137,8 @@ struct Amendments { REGISTER(fixInvalidTxFlags); REGISTER(fixFrozenLPTokenTransfer); REGISTER(DeepFreeze); + REGISTER(PermissionDelegation); + REGISTER(fixPayChanCancelAfter); // Obsolete but supported by libxrpl REGISTER(CryptoConditionsSuite); diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index 7139002d..ebd04e99 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -185,6 +185,13 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) ); auto const seq = input.permissionedDomain->at(JS(seq)).as_int64(); key = ripple::keylet::permissionedDomain(*account, seq).key; + } else if (input.delegate) { + auto const account = + ripple::parseBase58(boost::json::value_to(input.delegate->at(JS(account)))); + auto const authorize = + ripple::parseBase58(boost::json::value_to(input.delegate->at(JS(authorize))) + ); + key = ripple::keylet::delegate(*account, *authorize).key; } else { // Must specify 1 of the following fields to indicate what type if (ctx.apiVersion == 1) @@ -319,7 +326,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va {JS(oracle), ripple::ltORACLE}, {JS(credential), ripple::ltCREDENTIAL}, {JS(mptoken), ripple::ltMPTOKEN}, - {JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN} + {JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN}, + {JS(delegate), ripple::ltDELEGATE} }; auto const parseBridgeFromJson = [](boost::json::value const& bridgeJson) { @@ -408,6 +416,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va input.mptoken = jv.at(JS(mptoken)).as_object(); } else if (jsonObject.contains(JS(permissioned_domain))) { input.permissionedDomain = jv.at(JS(permissioned_domain)).as_object(); + } else if (jsonObject.contains(JS(delegate))) { + input.delegate = jv.at(JS(delegate)).as_object(); } if (jsonObject.contains("include_deleted")) diff --git a/src/rpc/handlers/LedgerEntry.hpp b/src/rpc/handlers/LedgerEntry.hpp index 9851ad74..54b5a5ad 100644 --- a/src/rpc/handlers/LedgerEntry.hpp +++ b/src/rpc/handlers/LedgerEntry.hpp @@ -110,6 +110,7 @@ public: std::optional createAccountClaimId; std::optional oracleNode; std::optional credential; + std::optional delegate; bool includeDeleted = false; }; @@ -392,6 +393,23 @@ public: }, }, }}}, + {JS(delegate), + meta::WithCustomError{ + validation::Type{}, Status(ClioError::RpcMalformedRequest) + }, + meta::IfType{kMALFORMED_REQUEST_HEX_STRING_VALIDATOR}, + meta::IfType{meta::Section{ + {JS(account), + meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)}, + meta::WithCustomError{ + validation::CustomValidators::accountBase58Validator, Status(ClioError::RpcMalformedAddress) + }}, + {JS(authorize), + meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)}, + meta::WithCustomError{ + validation::CustomValidators::accountBase58Validator, Status(ClioError::RpcMalformedAddress) + }} + }}}, {JS(ledger), check::Deprecated{}}, {"include_deleted", validation::Type{}}, }; diff --git a/tests/common/util/TestObject.cpp b/tests/common/util/TestObject.cpp index 664f7438..57cb88c4 100644 --- a/tests/common/util/TestObject.cpp +++ b/tests/common/util/TestObject.cpp @@ -1516,6 +1516,31 @@ createPermissionedDomainObject( return object; } +ripple::STObject +createDelegateObject( + std::string_view accountId, + std::string_view authorize, + std::string_view ledgerIndex, + uint64_t ownerNode, + ripple::uint256 previousTxId, + uint32_t previousTxSeq +) +{ + ripple::STObject object(ripple::sfLedgerEntry); + + object.setFieldH256(ripple::sfLedgerIndex, ripple::uint256(ledgerIndex)); + object.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDELEGATE); + object.setAccountID(ripple::sfAccount, getAccountIdWithString(accountId)); + object.setAccountID(ripple::sfAuthorize, getAccountIdWithString(authorize)); + object.setFieldArray(ripple::sfPermissions, ripple::STArray{}); + object.setFieldU64(ripple::sfOwnerNode, ownerNode); + object.setFieldH256(ripple::sfPreviousTxnID, previousTxId); + object.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxSeq); + object.setFieldU32(ripple::sfFlags, 0); + + return object; +} + ripple::STObject createOraclePriceData( uint64_t assetPrice, diff --git a/tests/common/util/TestObject.hpp b/tests/common/util/TestObject.hpp index 20b6682b..541a9089 100644 --- a/tests/common/util/TestObject.hpp +++ b/tests/common/util/TestObject.hpp @@ -464,6 +464,16 @@ createPermissionedDomainObject( uint32_t previousTxSeq ); +[[nodiscard]] ripple::STObject +createDelegateObject( + std::string_view accountId, + std::string_view authorize, + std::string_view ledgerIndex, + uint64_t ownerNode, + ripple::uint256 previousTxId, + uint32_t previousTxSeq +); + [[nodiscard]] ripple::STObject createOraclePriceData( uint64_t assetPrice, diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index b11c34b1..6399c831 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -2193,6 +2193,106 @@ generateTestValuesForParametersTest() .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, + ParamTestCaseBundle{ + .testName = "Delegate_InvalidType", + .testJson = R"json({"delegate": 123})json", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + ParamTestCaseBundle{ + .testName = "Delegate_InvalidStringIndex", + .testJson = R"json({"delegate": "invalid_hex_string"})json", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + ParamTestCaseBundle{ + .testName = "Delegate_EmptyObject", + .testJson = R"json({"delegate": {}})json", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + ParamTestCaseBundle{ + .testName = "Delegate_MissingAccount", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "authorize": "{}" + }} + }})json", + kACCOUNT2 + ), + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + ParamTestCaseBundle{ + .testName = "Delegate_AccountNotString", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "account": 123, + "authorize": "{}" + }} + }})json", + kACCOUNT2 + ), + .expectedError = "malformedAddress", + .expectedErrorMessage = "Malformed address." + }, + ParamTestCaseBundle{ + .testName = "Delegate_AccountInvalid", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "account": "invalid_address", + "authorize": "{}" + }} + }})json", + kACCOUNT2 + ), + .expectedError = "malformedAddress", + .expectedErrorMessage = "Malformed address." + }, + ParamTestCaseBundle{ + .testName = "Delegate_MissingAuthorize", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "account": "{}" + }} + }})json", + kACCOUNT + ), + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + ParamTestCaseBundle{ + .testName = "Delegate_AuthorizeNotString", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "account": "{}", + "authorize": 123 + }} + }})json", + kACCOUNT + ), + .expectedError = "malformedAddress", + .expectedErrorMessage = "Malformed address." + }, + ParamTestCaseBundle{ + .testName = "Delegate_AuthorizeInvalid", + .testJson = fmt::format( + R"json({{ + "delegate": {{ + "account": "{}", + "authorize": "invalid_address" + }} + }})json", + kACCOUNT + ), + .expectedError = "malformedAddress", + .expectedErrorMessage = "Malformed address." + }, }; } @@ -2957,7 +3057,36 @@ generateTestValuesForNormalPathTest() ripple::keylet::permissionedDomain(ripple::parseBase58(kACCOUNT).value(), kRANGE_MAX) .key, .mockedEntity = createPermissionedDomainObject(kACCOUNT, kINDEX1, kRANGE_MAX, 0, ripple::uint256{0}, 0) - } + }, + NormalPathTestBundle{ + .testName = "DelegateViaStringIndex", + .testJson = fmt::format( + R"json({{ + "binary": true, + "delegate": "{}" + }})json", + kINDEX1 + ), + .expectedIndex = ripple::uint256{kINDEX1}, + .mockedEntity = createDelegateObject(kACCOUNT, kACCOUNT2, kINDEX1, 0, ripple::uint256{0}, 0) + }, + NormalPathTestBundle{ + .testName = "DelegateViaObject", + .testJson = fmt::format( + R"json({{ + "binary": true, + "delegate": {{ + "account": "{}", + "authorize": "{}" + }} + }})json", + kACCOUNT, + kACCOUNT2 + ), + .expectedIndex = + ripple::keylet::delegate(getAccountIdWithString(kACCOUNT), getAccountIdWithString(kACCOUNT2)).key, + .mockedEntity = createDelegateObject(kACCOUNT, kACCOUNT2, kINDEX1, 0, ripple::uint256{0}, 0) + }, }; } diff --git a/tests/unit/rpc/handlers/TxTests.cpp b/tests/unit/rpc/handlers/TxTests.cpp index 6815bcdb..57bea26e 100644 --- a/tests/unit/rpc/handlers/TxTests.cpp +++ b/tests/unit/rpc/handlers/TxTests.cpp @@ -834,7 +834,7 @@ TEST_F(RPCTxTest, CTIDNotMatch) ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); - EXPECT_EQ(err.at("error").as_string(), "unknown"); + EXPECT_EQ(err.at("error").as_string(), "wrongNetwork"); EXPECT_EQ(err.at("error_code").as_uint64(), rpc::RippledError::rpcWRONG_NETWORK); EXPECT_EQ( err.at("error_message").as_string(),