diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index b44c0334f..de0189855 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -920,6 +920,7 @@ if (tests) src/test/protocol/InnerObjectFormats_test.cpp src/test/protocol/Issue_test.cpp src/test/protocol/Hooks_test.cpp + src/test/protocol/Memo_test.cpp src/test/protocol/PublicKey_test.cpp src/test/protocol/Quality_test.cpp src/test/protocol/STAccount_test.cpp diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index a840ba7e8..93562df05 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -246,19 +246,6 @@ EscrowCreate::doApply() JLOG(ctx_.journal.trace()) << "EscrowCreate::doApply trustTransferAllowed result=" << result; - // perform the lock as a dry run before - // we modify anything on-ledger - sleLine = ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency())); - if (!sleLine) - { - JLOG(ctx_.journal.trace()) - << "EscrowCreate::doApply trustAdjustLockedBalance trustline missing " - << account << "-" - << amount.getIssuer() << "/" - << amount.getCurrency(); - return tecUNFUNDED; - } - if (!isTesSuccess(result)) return result; diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 89b807a4d..0c7c0d168 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -1016,6 +1016,7 @@ private: } } + jvRequest[jss::signature] = jvParams[3u].asString(); return jvRequest; } @@ -1552,16 +1553,7 @@ struct RPCCallImp // callbackFuncP. // Receive reply - if (iStatus == 401) - Throw( - "incorrect rpcuser or rpcpassword (authorization failed)"); - else if ( - (iStatus >= 400) && (iStatus != 400) && (iStatus != 404) && - (iStatus != 500)) // ? - Throw( - std::string("server returned HTTP error ") + - std::to_string(iStatus)); - else if (strData.empty()) + if (strData.empty()) Throw("no response from server"); // Parse reply diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index a2ef7e62e..43a07a4f6 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -164,12 +164,15 @@ enum warning_code_i { namespace RPC { -/** Maps an rpc error code to its token and default message. */ +/** Maps an rpc error code to its token, default message, and HTTP status. */ struct ErrorInfo { // Default ctor needed to produce an empty std::array during constexpr eval. constexpr ErrorInfo() - : code(rpcUNKNOWN), token("unknown"), message("An unknown error code.") + : code(rpcUNKNOWN) + , token("unknown") + , message("An unknown error code.") + , http_status(200) { } @@ -177,13 +180,26 @@ struct ErrorInfo error_code_i code_, char const* token_, char const* message_) - : code(code_), token(token_), message(message_) + : code(code_), token(token_), message(message_), http_status(200) + { + } + + constexpr ErrorInfo( + error_code_i code_, + char const* token_, + char const* message_, + int http_status_) + : code(code_) + , token(token_) + , message(message_) + , http_status(http_status_) { } error_code_i code; Json::StaticString token; Json::StaticString message; + int http_status; }; /** Returns an ErrorInfo that reflects the error code. */ @@ -333,6 +349,10 @@ not_validator_error() bool contains_error(Json::Value const& json); +/** Returns http status that corresponds to the error code. */ +int +error_code_http_status(error_code_i code); + } // namespace RPC /** Returns a single string with the contents of an RPC error. */ diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 34b939d89..e8862cb18 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -343,6 +343,7 @@ extern uint256 const featureBalanceRewards; extern uint256 const featureDisallowIncoming; extern uint256 const fixRemoveNFTokenAutoTrustLine; extern uint256 const featureImmediateOfferKilled; +extern uint256 const featureDisallowIncoming; extern uint256 const featurePaychanAndEscrowForTokens; extern uint256 const featureURIToken; diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index e4a9acf46..bb3b2d47a 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -26,105 +27,96 @@ namespace RPC { namespace detail { -// clang-format off // Unordered array of ErrorInfos, so we don't have to maintain the list // ordering by hand. // // This array will be omitted from the object file; only the sorted version // will remain in the object file. But the string literals will remain. -constexpr static ErrorInfo unorderedErrorInfos[]{ - {rpcACT_MALFORMED, "actMalformed", "Account malformed."}, - {rpcACT_NOT_FOUND, "actNotFound", "Account not found."}, - {rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."}, - {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, - {rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade."}, - {rpcEXPIRED_VALIDATOR_LIST, "unlBlocked", "Validator list expired."}, - {rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range."}, - {rpcBAD_KEY_TYPE, "badKeyType", "Bad key type."}, - {rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."}, - {rpcBAD_ISSUER, "badIssuer", "Issuer account malformed."}, - {rpcBAD_MARKET, "badMarket", "No such market."}, - {rpcBAD_SECRET, "badSecret", "Secret does not match account."}, - {rpcBAD_SEED, "badSeed", "Disallowed seed."}, - {rpcBAD_SYNTAX, "badSyntax", "Syntax error."}, - {rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed."}, - {rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed", "Payment channel amount is malformed."}, - {rpcCOMMAND_MISSING, "commandMissing", "Missing command entry."}, - {rpcDB_DESERIALIZATION, "dbDeserialization", "Database deserialization error."}, - {rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed."}, - {rpcDST_ACT_MISSING, "dstActMissing", "Destination account not provided."}, - {rpcDST_ACT_NOT_FOUND, "dstActNotFound", "Destination account not found."}, - {rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed."}, - {rpcDST_AMT_MISSING, "dstAmtMissing", "Destination amount/currency/issuer is missing."}, - {rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed."}, - {rpcEXCESSIVE_LGR_RANGE, "excessiveLgrRange", "Ledger range exceeds 1000."}, - {rpcFORBIDDEN, "forbidden", "Bad credentials."}, - {rpcFAILED_TO_FORWARD, "failedToForward", "Failed to forward request to p2p node"}, - {rpcHIGH_FEE, "highFee", "Current transaction fee exceeds your limit."}, - {rpcINTERNAL, "internal", "Internal error."}, - {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid."}, - {rpcINVALID_PARAMS, "invalidParams", "Invalid parameters."}, - {rpcJSON_RPC, "json_rpc", "JSON-RPC transport error."}, - {rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid."}, - {rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed."}, - {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."}, - {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."}, - {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."}, - {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."}, - {rpcNOT_IMPL, "notImpl", "Not implemented."}, - {rpcNOT_READY, "notReady", "Not ready to handle this request."}, - {rpcNOT_SUPPORTED, "notSupported", "Operation not supported."}, - {rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable."}, - {rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable."}, - {rpcNOT_SYNCED, "notSynced", "Not synced to the network."}, - {rpcNO_EVENTS, "noEvents", "Current transport does not support events."}, - {rpcNO_NETWORK, "noNetwork", "Not synced to the network."}, - {rpcNO_PERMISSION, "noPermission", "You don't have permission for this command."}, - {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress."}, - {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed."}, - {rpcREPORTING_UNSUPPORTED, "reportingUnsupported", "Requested operation not supported by reporting mode server"}, - {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed."}, - {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server."}, - {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed."}, - {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided."}, - {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found."}, - {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed."}, - {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed."}, - {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed."}, - {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now."}, - {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found."}, - {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method."}, - {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed."}, - {rpcOBJECT_NOT_FOUND, "objectNotFound", "The requested object was not found."}}; -// clang-format on - -// C++ does not allow you to return an array from a function. You must -// return an object which may in turn contain an array. The following -// struct is simply defined so the enclosed array can be returned from a -// constexpr function. // -// In C++17 this struct can be replaced by a std::array. But in C++14 -// the constexpr methods of a std::array are not sufficient to perform the -// necessary work at compile time. -template -struct ErrorInfoArray -{ - // Visual Studio doesn't treat a templated aggregate as an aggregate. - // So, for Visual Studio, we define a constexpr default constructor. - constexpr ErrorInfoArray() : infos{} - { - } +// There's a certain amount of tension in determining the correct HTTP +// status to associate with a given RPC error. Initially all RPC errors +// returned 200 (OK). And that's the default behavior if no HTTP status code +// is specified below. +// +// The codes currently selected target the load balancer fail-over use case. +// If a query fails on one node but is likely to have a positive outcome +// on a different node, then the failure should return a 4xx/5xx range +// status code. - ErrorInfo infos[N]; -}; +// clang-format off +constexpr static ErrorInfo unorderedErrorInfos[]{ + {rpcACT_MALFORMED, "actMalformed", "Account malformed."}, + {rpcACT_NOT_FOUND, "actNotFound", "Account not found."}, + {rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."}, + {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, + {rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade.", 503}, + {rpcEXPIRED_VALIDATOR_LIST, "unlBlocked", "Validator list expired.", 503}, + {rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range.", 400}, + {rpcBAD_KEY_TYPE, "badKeyType", "Bad key type.", 400}, + {rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid.", 500}, + {rpcBAD_ISSUER, "badIssuer", "Issuer account malformed.", 400}, + {rpcBAD_MARKET, "badMarket", "No such market.", 404}, + {rpcBAD_SECRET, "badSecret", "Secret does not match account.", 403}, + {rpcBAD_SEED, "badSeed", "Disallowed seed.", 403}, + {rpcBAD_SYNTAX, "badSyntax", "Syntax error.", 400}, + {rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed.", 400}, + {rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed", "Payment channel amount is malformed.", 400}, + {rpcCOMMAND_MISSING, "commandMissing", "Missing command entry.", 400}, + {rpcDB_DESERIALIZATION, "dbDeserialization", "Database deserialization error.", 502}, + {rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed.", 400}, + {rpcDST_ACT_MISSING, "dstActMissing", "Destination account not provided.", 400}, + {rpcDST_ACT_NOT_FOUND, "dstActNotFound", "Destination account not found.", 404}, + {rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed.", 400}, + {rpcDST_AMT_MISSING, "dstAmtMissing", "Destination amount/currency/issuer is missing.", 400}, + {rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed.", 400}, + {rpcEXCESSIVE_LGR_RANGE, "excessiveLgrRange", "Ledger range exceeds 1000.", 400}, + {rpcFORBIDDEN, "forbidden", "Bad credentials.", 403}, + {rpcFAILED_TO_FORWARD, "failedToForward", "Failed to forward request to p2p node", 503}, + {rpcHIGH_FEE, "highFee", "Current transaction fee exceeds your limit.", 402}, + {rpcINTERNAL, "internal", "Internal error.", 500}, + {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid.", 400}, + {rpcINVALID_PARAMS, "invalidParams", "Invalid parameters.", 400}, + {rpcJSON_RPC, "json_rpc", "JSON-RPC transport error.", 500}, + {rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid.", 400}, + {rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed.", 400}, + {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found.", 404}, + {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated.", 202}, + {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled.", 403}, + {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration.", 501}, + {rpcNOT_IMPL, "notImpl", "Not implemented.", 501}, + {rpcNOT_READY, "notReady", "Not ready to handle this request.", 503}, + {rpcNOT_SUPPORTED, "notSupported", "Operation not supported.", 501}, + {rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable.", 503}, + {rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable.", 503}, + {rpcNOT_SYNCED, "notSynced", "Not synced to the network.", 503}, + {rpcNO_EVENTS, "noEvents", "Current transport does not support events.", 405}, + {rpcNO_NETWORK, "noNetwork", "Not synced to the network.", 503}, + {rpcNO_PERMISSION, "noPermission", "You don't have permission for this command.", 401}, + {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress.", 404}, + {rpcOBJECT_NOT_FOUND, "objectNotFound", "The requested object was not found.", 404}, + {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed.", 400}, + {rpcREPORTING_UNSUPPORTED, "reportingUnsupported", "Requested operation not supported by reporting mode server", 405}, + {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed.", 400}, + {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed.", 400}, + {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.", 429}, + {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed.", 400}, + {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided.", 400}, + {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found.", 404}, + {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed.", 400}, + {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed.", 400}, + {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed.", 400}, + {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503}, + {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found.", 404}, + {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405}}; +// clang-format on // Sort and validate unorderedErrorInfos at compile time. Should be // converted to consteval when get to C++20. template constexpr auto -sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray +sortErrorInfos(ErrorInfo const (&unordered)[N]) -> std::array { - ErrorInfoArray ret; + std::array ret = {}; for (ErrorInfo const& info : unordered) { @@ -135,12 +127,10 @@ sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray static_assert(rpcSUCCESS == 0, "Unexpected error_code_i layout."); int const index{info.code - 1}; - if (ret.infos[index].code != rpcUNKNOWN) + if (ret[index].code != rpcUNKNOWN) throw(std::invalid_argument("Duplicate error_code_i in list")); - ret.infos[index].code = info.code; - ret.infos[index].token = info.token; - ret.infos[index].message = info.message; + ret[index] = info; } // Verify that all entries are filled in starting with 1 and proceeding @@ -150,7 +140,7 @@ sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray // rpcUNKNOWN. But other than that all entries should match their index. int codeCount{0}; int expect{rpcBAD_SYNTAX - 1}; - for (ErrorInfo const& info : ret.infos) + for (ErrorInfo const& info : ret) { ++expect; if (info.code == rpcUNKNOWN) @@ -181,7 +171,7 @@ get_error_info(error_code_i code) { if (code <= rpcSUCCESS || code > rpcLAST) return detail::unknownError; - return detail::sortedErrorInfos.infos[code - 1]; + return detail::sortedErrorInfos[code - 1]; } Json::Value @@ -208,6 +198,12 @@ contains_error(Json::Value const& json) return false; } +int +error_code_http_status(error_code_i code) +{ + return get_error_info(code).http_status; +} + } // namespace RPC std::string diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index cb70fdcab..f269283b8 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -970,6 +971,29 @@ ServerHandlerImp::processRequest( } } } + + // If we're returning an error_code, use that to determine the HTTP status. + int const httpStatus = [&reply]() { + // This feature is enabled with ripplerpc version 3.0 and above. + // Before ripplerpc version 3.0 always return 200. + if (reply.isMember(jss::ripplerpc) && + reply[jss::ripplerpc].isString() && + reply[jss::ripplerpc].asString() >= "3.0") + { + // If there's an error_code, use that to determine the HTTP Status. + if (reply.isMember(jss::error) && + reply[jss::error].isMember(jss::error_code) && + reply[jss::error][jss::error_code].isInt()) + { + int const errCode = reply[jss::error][jss::error_code].asInt(); + return RPC::error_code_http_status( + static_cast(errCode)); + } + } + // Return OK. + return 200; + }(); + auto response = to_string(reply); rpc_time_.notify(std::chrono::duration_cast( @@ -988,7 +1012,7 @@ ServerHandlerImp::processRequest( stream << "Reply: " << response.substr(0, maxSize); } - HTTPReply(200, response, output, rpcJ); + HTTPReply(httpStatus, response, output, rpcJ); } //------------------------------------------------------------------------------ diff --git a/src/ripple/server/impl/JSONRPCUtil.cpp b/src/ripple/server/impl/JSONRPCUtil.cpp index f5bb815a9..12d12829c 100644 --- a/src/ripple/server/impl/JSONRPCUtil.cpp +++ b/src/ripple/server/impl/JSONRPCUtil.cpp @@ -61,7 +61,7 @@ HTTPReply( { JLOG(j.trace()) << "HTTP Reply " << nStatus << " " << content; - if (nStatus == 401) + if (content.empty() && nStatus == 401) { output("HTTP/1.0 401 Authorization Required\r\n"); output(getHTTPHeaderTimestamp()); @@ -100,18 +100,33 @@ HTTPReply( case 200: output("HTTP/1.1 200 OK\r\n"); break; + case 202: + output("HTTP/1.1 202 Accepted\r\n"); + break; case 400: output("HTTP/1.1 400 Bad Request\r\n"); break; + case 401: + output("HTTP/1.1 401 Authorization Required\r\n"); + break; case 403: output("HTTP/1.1 403 Forbidden\r\n"); break; case 404: output("HTTP/1.1 404 Not Found\r\n"); break; + case 405: + output("HTTP/1.1 405 Method Not Allowed\r\n"); + break; + case 429: + output("HTTP/1.1 429 Too Many Requests\r\n"); + break; case 500: output("HTTP/1.1 500 Internal Server Error\r\n"); break; + case 501: + output("HTTP/1.1 501 Not Implemented\r\n"); + break; case 503: output("HTTP/1.1 503 Server is overloaded\r\n"); break; diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 87a2c6a0e..5e54978ec 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -245,7 +245,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("simple"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -501,7 +501,7 @@ struct PayChan_test : public beast::unit_test::suite auto const carol = Account("carol"); { // If dst claims after cancel after, channel closes - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); auto const pk = alice.pk(); auto const settleDelay = 100s; @@ -533,7 +533,7 @@ struct PayChan_test : public beast::unit_test::suite } { // Third party can close after cancel after - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); auto const pk = alice.pk(); auto const settleDelay = 100s; @@ -561,7 +561,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("expiration"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -627,7 +627,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("settle delay"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -688,7 +688,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("close dry"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -723,7 +723,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("default amount"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -794,7 +794,7 @@ struct PayChan_test : public beast::unit_test::suite { // Create a channel where dst disallows XRP. Ignore that flag, // since it's just advisory. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); auto const chan = channel(alice, bob, env.seq(alice)); @@ -819,7 +819,7 @@ struct PayChan_test : public beast::unit_test::suite // Claim to a channel where dst disallows XRP (channel is // created before disallow xrp is set). Ignore that flag // since it is just advisory. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); auto const chan = channel(alice, bob, env.seq(alice)); env(create(alice, bob, XRP(1000), 3600s, alice.pk())); @@ -839,7 +839,7 @@ struct PayChan_test : public beast::unit_test::suite using namespace jtx; using namespace std::literals::chrono_literals; // Create a channel where dst disallows XRP - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -872,7 +872,7 @@ struct PayChan_test : public beast::unit_test::suite auto const bob = Account("bob"); auto const carol = Account("carol"); { - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); env(fset(bob, asfDepositAuth)); @@ -991,7 +991,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("Multiple channels to the same account"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -1014,7 +1014,7 @@ struct PayChan_test : public beast::unit_test::suite using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); @@ -1082,7 +1082,7 @@ struct PayChan_test : public beast::unit_test::suite return r; }(); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice); for (auto const& a : bobs) { @@ -1190,7 +1190,7 @@ struct PayChan_test : public beast::unit_test::suite auto const alice = Account("alice"); auto const bob = Account("bob"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); // Create a channel from alice to bob and from bob to alice @@ -1221,7 +1221,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("PayChan Auth/Verify RPC"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); @@ -1573,7 +1573,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("Optional Fields"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -1624,7 +1624,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("malformed pk"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -1738,7 +1738,7 @@ struct PayChan_test : public beast::unit_test::suite { // Test with adding the paychan to the recipient's owner directory - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); @@ -2032,7 +2032,7 @@ struct PayChan_test : public beast::unit_test::suite testcase("using tickets"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -2650,11 +2650,11 @@ struct PayChan_test : public beast::unit_test::suite ter(temBAD_EXPIRATION)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); env(fund(alice, chan, USD(1), NetClock::time_point{minExpiration})); - // env.close(minExpiration); - // // Try to extend the expiration after the expiration has already passed - // env(fund( - // alice, chan, USD(1), NetClock::time_point{minExpiration + 1000s})); - // BEAST_EXPECT(!channelExists(*env.current(), chan)); + env.close(minExpiration); + // Try to extend the expiration after the expiration has already passed + env(fund( + alice, chan, USD(1), NetClock::time_point{minExpiration + 1000s})); + BEAST_EXPECT(!channelExists(*env.current(), chan)); } void @@ -6079,6 +6079,1440 @@ struct PayChan_test : public beast::unit_test::suite testICPrecisionLoss(features); } +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + // testWithFeats(all - disallowIncoming); + testWithFeats(all); + } + + void + testICRippleState(FeatureBitset features) + { + testcase("IC RippleState"); + using namespace test::jtx; + using namespace std::literals; + + // + // USE balance(env, ...) over env.balance(...) + // I did this to check the exact sign "-/+" + // + + // src > dst + // src > issuer + // dest no trustline + // negative locked/tl balance + { + auto const src = Account("alice2"); + auto const dst = Account("bob0"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(5000), src, dst, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == -USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - delta); + BEAST_EXPECT(preLocked == postLocked - delta); + // src claim fails because trust limit is 0 + env(claim(src, chan, authAmt, authAmt), ter(tecPATH_DRY)); + } + // src < dst + // src < issuer + // dest no trustline + // positive locked/tl balance + { + auto const src = Account("carol0"); + auto const dst = Account("dan1"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(5000), src, dst, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + delta); + BEAST_EXPECT(preLocked == postLocked + delta); + // src claim fails because trust limit is 0 + env(claim(src, chan, authAmt, authAmt), ter(tecPATH_DRY)); + } + // dst > src + // dst > issuer + // dest no trustline + // negative locked/tl balance + { + auto const src = Account("dan1"); + auto const dst = Account("alice2"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(5000), src, dst, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == -USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - delta); + BEAST_EXPECT(preLocked == postLocked - delta); + // src claim fails because trust limit is 0 + env(claim(src, chan, authAmt, authAmt), ter(tecPATH_DRY)); + } + // dst < src + // dst < issuer + // dest no trustline + // positive locked/tl balance + { + auto const src = Account("bob0"); + auto const dst = Account("carol0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(5000), src, dst, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = -lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + delta); + BEAST_EXPECT(preLocked == postLocked + delta); + // src claim fails because trust limit is 0 + env(claim(src, chan, authAmt, authAmt), ter(tecPATH_DRY)); + } + // src > dst + // src > issuer + // dest trustline + // negative locked/tl balance + { + auto const src = Account("alice2"); + auto const dst = Account("bob0"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, dst, gw); + env.close(); + env.trust(USD(100000), src, dst); + env.close(); + env(pay(gw, src, USD(10000))); + env(pay(gw, dst, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = -lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == -USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - delta); + BEAST_EXPECT(preLocked == postLocked - delta); + env(claim(src, chan, authAmt, authAmt)); + env.close(); + postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + authAmt); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - authAmt); + BEAST_EXPECT(preLocked == postLocked - authAmt); + } + // src < dst + // src < issuer + // dest trustline + // positive locked/tl balance + { + auto const src = Account("carol0"); + auto const dst = Account("dan1"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, dst, gw); + env.close(); + env.trust(USD(100000), src, dst); + env.close(); + env(pay(gw, src, USD(10000))); + env(pay(gw, dst, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + delta); + BEAST_EXPECT(preLocked == postLocked + delta); + env(claim(src, chan, authAmt, authAmt)); + env.close(); + postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - authAmt); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + authAmt); + BEAST_EXPECT(preLocked == postLocked + authAmt); + } + // dst > src + // dst > issuer + // dest trustline + // negative locked/tl balance + { + auto const src = Account("dan1"); + auto const dst = Account("alice2"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, dst, gw); + env.close(); + env.trust(USD(100000), src, dst); + env.close(); + env(pay(gw, src, USD(10000))); + env(pay(gw, dst, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == -USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - delta); + BEAST_EXPECT(preLocked == postLocked - delta); + env(claim(src, chan, authAmt, authAmt)); + env.close(); + postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc + authAmt); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst - authAmt); + BEAST_EXPECT(preLocked == postLocked - authAmt); + } + // dst < src + // dst < issuer + // dest trustline + // positive locked/tl balance + { + auto const src = Account("bob0"); + auto const dst = Account("carol0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, dst, gw); + env.close(); + env.trust(USD(100000), src, dst); + env.close(); + env(pay(gw, src, USD(10000))); + env(pay(gw, dst, USD(10000))); + env.close(); + + // src can create paychan + auto const pk = src.pk(); + auto const settleDelay = 100s; + auto const chan = channel(src, dst, env.seq(src)); + auto preLocked = -lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(0)); + env(create(src, dst, USD(1000), settleDelay, pk)); + env.close(); + + preLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + + // dst can claim paychan + auto const preSrc = lineBalance(env, src, gw, USD); + auto const preDst = lineBalance(env, dst, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = USD(1000); + auto const sig = signClaimICAuth(src.pk(), src.sk(), chan, authAmt); + env(claim(dst, chan, reqBal, authAmt, Slice(sig), src.pk())); + env.close(); + auto postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - delta); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + delta); + BEAST_EXPECT(preLocked == postLocked + delta); + env(claim(src, chan, authAmt, authAmt)); + env.close(); + postLocked = lockedAmount(env, src, gw, USD); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == preSrc - authAmt); + BEAST_EXPECT(lineBalance(env, dst, gw, USD) == preDst + authAmt); + BEAST_EXPECT(preLocked == postLocked + authAmt); + } + } + + void + testICGateway(FeatureBitset features) + { + testcase("IC Gateway"); + using namespace test::jtx; + using namespace std::literals; + + // issuer -> src + // src > issuer + // dest no trustline + // negative locked/tl balance + { + auto const src = Account("alice2"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(0)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == -USD(500)); + } + // issuer -> src + // src < issuer + // dest no trustline + // positive locked/tl balance + { + auto const src = Account("carol0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(0)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == USD(500)); + } + // issuer -> src + // dst > issuer + // dest no trustline + // negative locked/tl balance + { + auto const src = Account("dan1"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(0)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == -USD(500)); + } + // issuer -> src + // dst < issuer + // dest no trustline + // positive locked/tl balance + { + auto const src = Account("bob0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(0)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == USD(500)); + } + // issuer -> src + // src > issuer + // dest has trustline + // negative locked/tl balance + { + auto const src = Account("alice2"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == -USD(10000)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == -USD(10500)); + } + // issuer -> src + // src < issuer + // dest has trustline + // positive locked/tl balance + { + auto const src = Account("carol0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(10000)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == USD(10500)); + } + // issuer -> src + // dst > issuer + // dest has trustline + // negative locked/tl balance + { + auto const src = Account("dan1"); + auto const gw = Account{"gw0"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == -USD(10000)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == -USD(10500)); + } + // issuer -> src + // dst < issuer + // dest has trustline + // positive locked/tl balance + { + auto const src = Account("bob0"); + auto const gw = Account{"gw1"}; + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(10000), src, gw); + env.close(); + env.trust(USD(100000), src); + env.close(); + env(pay(gw, src, USD(10000))); + env.close(); + + // issuer can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, src, env.seq(gw)); + env(create(gw, src, USD(1000), settleDelay, pk)); + env.close(); + + // src can claim paychan without trustline + auto const preSrc = lineBalance(env, src, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(src, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + BEAST_EXPECT(preSrc == USD(10000)); + BEAST_EXPECT(lineBalance(env, src, gw, USD) == USD(10500)); + } + // alice -> issuer + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + env.fund(XRP(10000), alice, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // alice can create paychan + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, gw, env.seq(alice)); + env(create(alice, gw, USD(1000), settleDelay, pk)); + env.close(); + + // issuer can claim paychan + auto const preAlice = env.balance(alice, USD.issue()); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(gw, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env.close(); + BEAST_EXPECT(preAlice == USD(10000)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + std::cout << "PRE ALICE: " << preAlice << "\n"; + std::cout << "POST ALICE: " << env.balance(alice, USD.issue()) + << "\n"; + } + } + + void + testICLockedRate(FeatureBitset features) + { + testcase("IC Locked Rate"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test locked rate + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, USD.issue()); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + auto const transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1000000000 * 1.25)); + + // alice can claim paychan + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(125); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(500); + env(claim(alice, chan, reqBal, authAmt)); + env.close(); + auto const postLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(875)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + // test rate change + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + auto transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1000000000 * 1.25)); + + auto const preAlice = env.balance(alice, USD.issue()); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(100); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(500); + + // alice can fund paychan + env(fund(alice, chan, USD(1000))); + env.close(); + transferRate = channelRate(*env.current(), chan); + // no change in rate + BEAST_EXPECT( + transferRate.value == std::uint32_t(1000000000 * 1.25)); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // alice can fund after issuer rate change + env(fund(alice, chan, USD(1000))); + env.close(); + transferRate = channelRate(*env.current(), chan); + // rate changed to new lower rate + BEAST_EXPECT( + transferRate.value == std::uint32_t(1000000000 * 1.00)); + + // issuer changes rate higher + env(rate(gw, 1.01)); + env.close(); + + // alice cant fund after issuer rate change + env(fund(alice, chan, USD(1000)), ter(temBAD_TRANSFER_RATE)); + env.close(); + transferRate = channelRate(*env.current(), chan); + // issuer rate stays at lower rate + BEAST_EXPECT( + transferRate.value == std::uint32_t(1000000000 * 1.00)); + } + // test issuer doesnt pay own rate + { + Env env{*this, features}; + env.fund(XRP(10000), alice, gw); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // issuer with rate can create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, alice, env.seq(gw)); + env(create(gw, alice, USD(1000), settleDelay, pk)); + env.close(); + + // issuer can claim paychan, alice has trustline + auto const preLocked = -lockedAmount(env, alice, gw, USD); + auto const preAlice = env.balance(alice, USD.issue()); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + env(claim(gw, chan, reqBal, authAmt)); + env.close(); + auto const postLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); + } + } + + void + testICTLLimitAmount(FeatureBitset features) + { + testcase("IC Trustline Limit Amount"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + + // test LimitAmount + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice); + env.trust(USD(1000), bob); + env.close(); + env(pay(gw, alice, USD(1000))); + env(pay(gw, bob, USD(1000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); + auto preBob = env.balance(bob); + auto const delta = USD(50); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const preLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + // alice cannot claim because bobs amount would be > than limit + env(claim(alice, chan, reqBal, authAmt), ter(tecPATH_DRY)); + + // bob can claim, increasing the limit amount + // auto const preBobLimit = limitAmount(env, bob, gw, USD); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env.close(); + // auto const postBobLimit = limitAmount(env, bob, gw, USD); + // bobs limit is NOT changed + // BEAST_EXPECT(postBobLimit == preBobLimit); + } + } + + void + testICTLRequireAuth(FeatureBitset features) + { + testcase("IC Trustline Require Auth"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + + // test asfRequireAuth + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob, gw); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, aliceUSD(10000)), txflags(tfSetfAuth)); + env(trust(alice, USD(10000))); + env.close(); + env(pay(gw, alice, USD(1000))); + env.close(); + + // auto const gwLimit = limitAmount(env, gw, alice, aliceUSD); + // auto const aliceLimit = limitAmount(env, alice, gw, USD); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto chan = channel(alice, bob, env.seq(alice)); + // alice cannot create because bob's trustline is not authorized + // all parties must be authorized + env(create(alice, bob, USD(1000), settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + + env(trust(gw, bobUSD(10000)), txflags(tfSetfAuth)); + env(trust(bob, USD(10000))); + env.close(); + env(pay(gw, bob, USD(1000))); + env.close(); + + // alice can now create because bob's trustline is authorized + chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(50); + auto const reqBal = delta; + auto const authAmt = reqBal + USD(100); + // alice can claim + env(claim(alice, chan, reqBal, authAmt)); + env.close(); + + // bob can claim + chanBal = channelBalance(*env.current(), chan); + chanAmt = channelAmount(*env.current(), chan); + auto const newReqBal = chanBal + delta; + auto const newAuthAmt = newReqBal + USD(100); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, newAuthAmt); + env(claim( + bob, chan, newReqBal, newAuthAmt, Slice(sig), alice.pk())); + env.close(); + } + } + void + testICTLFreeze(FeatureBitset features) + { + testcase("IC Trustline Freeze"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + // test Global Freeze + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + env(fset(gw, asfGlobalFreeze)); + env.close(); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk), ter(tecFROZEN)); + env.close(); + env(fclear(gw, asfGlobalFreeze)); + env.close(); + chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + env(fset(gw, asfGlobalFreeze)); + env.close(); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(10); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + env(claim(alice, chan, reqBal, authAmt), ter(tecFROZEN)); + auto sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + ter(tecFROZEN)); + env.close(); + env(fclear(gw, asfGlobalFreeze)); + env.close(); + env(claim(alice, chan, reqBal, authAmt)); + env.close(); + chanBal = channelBalance(*env.current(), chan); + chanAmt = channelAmount(*env.current(), chan); + reqBal = chanBal + delta; + authAmt = reqBal + USD(100); + sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env.close(); + } + // test Individual Freeze + { + // Env Setup + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env(trust(alice, USD(100000))); + env(trust(bob, USD(100000))); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // set freeze on alice trustline + env(trust(gw, USD(100000), alice, tfSetFreeze)); + env.close(); + + // setup transaction + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto chan = channel(alice, bob, env.seq(alice)); + + // create paychan fails - frozen trustline + env(create(alice, bob, USD(1000), settleDelay, pk), ter(tecFROZEN)); + env.close(); + + // clear freeze on alice trustline + env(trust(gw, USD(100000), alice, tfClearFreeze)); + env.close(); + + // create paychan success + chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + + // set freeze on alice trustline + env(trust(gw, USD(100000), alice, tfSetFreeze)); + env.close(); + + // paychan fields + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(10); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + + // alice claim paychan fails - frozen trustline + env(claim(alice, chan, reqBal, authAmt), ter(tecFROZEN)); + + // bob claim paychan fails - frozen trustline + auto sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + ter(tecFROZEN)); + env.close(); + + // clear freeze on bob trustline + env(trust(gw, USD(100000), bob, tfClearFreeze)); + // clear freeze on alice trustline + env(trust(gw, USD(100000), alice, tfClearFreeze)); + env.close(); + + // alice claim paychan success + env(claim(alice, chan, reqBal, authAmt)); + env.close(); + + // paychan fields + chanBal = channelBalance(*env.current(), chan); + chanAmt = channelAmount(*env.current(), chan); + reqBal = chanBal + delta; + authAmt = reqBal + USD(100); + + // bob claim paychan success + sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env.close(); + } + } + void + testICTLINSF(FeatureBitset features) + { + testcase("IC Trustline Insuficient Funds"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + { + // test pay more than locked amount + // ie. has 10000, lock 1000 then try to pay 10000 + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); + auto preBob = env.balance(bob); + auto const delta = USD(50); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const preLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + env(pay(alice, gw, USD(10000)), ter(tecPATH_PARTIAL)); + } + { + // test lock more than balance + locked + // ie. has 10000 lock 1000 then try to lock 10000 + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); + auto preBob = env.balance(bob); + auto const delta = USD(50); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const preLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + env(create(alice, bob, USD(10000), settleDelay, pk), + ter(tecINSUFFICIENT_FUNDS)); + } + } + + void + testICMismatchFunding(FeatureBitset features) + { + testcase("IC Mismatch Funding"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + auto const EUR = gw["EUR"]; + + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.trust(EUR(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, alice, EUR(10000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + env(fund(alice, chan, EUR(1000)), ter(temBAD_CURRENCY)); + } + } + + void + testICPrecisionLoss(FeatureBitset features) + { + testcase("IC Precision Loss"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test max trustline precision loss + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000000000000000), alice); + env.trust(USD(100000000000000000), bob); + env.close(); + env(pay(gw, alice, USD(10000000000000000))); + env(pay(gw, bob, USD(10000000000000000))); + env.close(); + + // setup tx + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + + // create paychan fails - precision loss + env(create(alice, bob, USD(10000000000000000), settleDelay, pk)); + env.close(); + + // alice cannot fund again - precision loss + env(fund(alice, chan, USD(1)), ter(tecPRECISION_LOSS)); + env.close(); + + // setup tx + auto const preAlice = env.balance(alice, USD.issue()); + auto const preLocked = -lockedAmount(env, alice, gw, USD); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(10000000000000000); + auto reqBal = chanBal + delta; + + // create paychan success + env(claim(alice, chan, reqBal, reqBal)); + env.close(); + auto postLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(0)); + + // alice can fund again + env(pay(gw, alice, USD(1))); + env.close(); + env(fund(alice, chan, USD(1))); + env.close(); + postLocked = -lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(1)); + } + // test min create precision loss + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(1000000000000000000), alice); + env.trust(USD(1000000000000000000), bob); + env.close(); + env(pay(gw, alice, USD(100000000000000000))); + env(pay(gw, bob, USD(1))); + env.close(); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + // alice cannot create paychan for 1/10/100 token - precision loss + env(create(alice, bob, USD(1), settleDelay, pk), + ter(tecPRECISION_LOSS)); + env.close(); + // alice can create paychan for 1000 token + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(10000), settleDelay, pk)); + env.close(); + + auto const chanBal = channelBalance(*env.current(), chan); + auto const chanAmt = channelAmount(*env.current(), chan); + auto reqBal = USD(1000); + auto authAmt = reqBal + USD(1000); + auto sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + ter(tecPRECISION_LOSS)); + + reqBal = USD(10000); + authAmt = reqBal + USD(10000); + sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + } + } + + void + testWithFeats(FeatureBitset features) + { + testSimple(features); + testCancelAfter(features); + testSettleDelay(features); + testExpiration(features); + testCloseDry(features); + testDefaultAmount(features); + testDisallowXRP(features); + testDstTag(features); + testDepositAuth(features); + testMultiple(features); + testAccountChannelsRPC(features); + testAccountChannelsRPCMarkers(features); + testAccountChannelsRPCSenderOnly(features); + testAuthVerifyRPC(features); + testOptionalFields(features); + testMalformedPK(features); + testMetaAndOwnership(features); + testAccountDelete(features); + testUsingTickets(features); + testICSimple(features); + testICCancelAfter(features); + testICSettleDelay(features); + testICExpiration(features); + testICCloseDry(features); + testICDefaultAmount(features); + testICDisallowXRP(features); + testICDstTag(features); + testICDepositAuth(features); + testICMultiple(features); + testICAccountChannelsRPC(features); + testICAccountChannelsRPCMarkers(features); + testICAccountChannelsRPCSenderOnly(features); + testICAuthVerifyRPC(features); + testICOptionalFields(features); + testICMalformedPK(features); + testICMetaAndOwnership(features); + testICAccountDelete(features); + testICUsingTickets(features); + testICAutoTL(features); + testICRippleState(features); + testICGateway(features); + testICLockedRate(features); + testICTLRequireAuth(features); + testICTLFreeze(features); + testICTLINSF(features); + testICMismatchFunding(features); + testICPrecisionLoss(features); + } + public: void run() override diff --git a/src/test/protocol/Memo_test.cpp b/src/test/protocol/Memo_test.cpp new file mode 100644 index 000000000..b39482e42 --- /dev/null +++ b/src/test/protocol/Memo_test.cpp @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace ripple { + +class Memo_test : public beast::unit_test::suite +{ +public: + void + testMemos() + { + testcase("Test memos"); + + using namespace test::jtx; + Account alice{"alice"}; + + Env env(*this); + env.fund(XRP(10000), alice); + env.close(); + + // Lambda that returns a valid JTx with a memo that we can hack up. + // This is the basis for building tests of invalid states. + auto makeJtxWithMemo = [&env, &alice]() { + JTx example = noop(alice); + memo const exampleMemo{"tic", "tac", "toe"}; + exampleMemo(env, example); + return example; + }; + + // A valid memo. + env(makeJtxWithMemo()); + env.close(); + + { + // Make sure that too big a memo is flagged as invalid. + JTx memoSize = makeJtxWithMemo(); + memoSize.jv[sfMemos.jsonName][0u][sfMemo.jsonName] + [sfMemoData.jsonName] = std::string(2020, '0'); + env(memoSize, ter(temINVALID)); + + // This memo is just barely small enough. + memoSize.jv[sfMemos.jsonName][0u][sfMemo.jsonName] + [sfMemoData.jsonName] = std::string(2018, '1'); + env(memoSize); + } + { + // Put a non-Memo in the Memos array. + JTx memoNonMemo = noop(alice); + auto& jv = memoNonMemo.jv; + auto& ma = jv[sfMemos.jsonName]; + auto& mi = ma[ma.size()]; + auto& m = mi[sfCreatedNode.jsonName]; // CreatedNode in Memos + m[sfMemoData.jsonName] = "3030303030"; + + env(memoNonMemo, ter(temINVALID)); + } + { + // Put an invalid field in a Memo object. + JTx memoExtra = makeJtxWithMemo(); + memoExtra + .jv[sfMemos.jsonName][0u][sfMemo.jsonName][sfFlags.jsonName] = + 13; + env(memoExtra, ter(temINVALID)); + } + { + // Put a character that is not allowed in a URL in a MemoType field. + JTx memoBadChar = makeJtxWithMemo(); + memoBadChar.jv[sfMemos.jsonName][0u][sfMemo.jsonName] + [sfMemoType.jsonName] = + strHex(std::string_view("ONE