Files
rippled/src/test/app/Oracle_test.cpp
Jingchen 508937f3d1 refactor: Retire MultiSignReserve and ExpandedSignerList amendments (#5981)
Amendments activated for more than 2 years can be retired. This change retires the MultiSignReserve and ExpandedSignerList amendments.
2025-11-13 11:42:45 +00:00

875 lines
31 KiB
C++

#include <test/jtx/Oracle.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
namespace test {
namespace jtx {
namespace oracle {
struct Oracle_test : public beast::unit_test::suite
{
private:
void
testInvalidSet()
{
testcase("Invalid Set");
using namespace jtx;
Account const owner("owner");
{
// Invalid account
Env env(*this);
Account const bad("bad");
env.memoize(bad);
Oracle oracle(
env,
{.owner = bad,
.seq = seq(1),
.fee = static_cast<int>(env.current()->fees().base.drops()),
.err = ter(terNO_ACCOUNT)});
}
// Insufficient reserve
{
Env env(*this);
env.fund(env.current()->fees().accountReserve(0), owner);
Oracle oracle(
env,
{.owner = owner,
.fee = static_cast<int>(env.current()->fees().base.drops()),
.err = ter(tecINSUFFICIENT_RESERVE)});
}
// Insufficient reserve if the data series extends to greater than 5
{
Env env(*this);
env.fund(
env.current()->fees().accountReserve(1) +
env.current()->fees().base * 2,
owner);
Oracle oracle(
env,
{.owner = owner,
.fee = static_cast<int>(env.current()->fees().base.drops())});
BEAST_EXPECT(oracle.exists());
oracle.set(UpdateArg{
.series =
{
{"XRP", "EUR", 740, 1},
{"XRP", "GBP", 740, 1},
{"XRP", "CNY", 740, 1},
{"XRP", "CAD", 740, 1},
{"XRP", "AUD", 740, 1},
},
.fee = static_cast<int>(env.current()->fees().base.drops()),
.err = ter(tecINSUFFICIENT_RESERVE)});
}
{
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
// Invalid flag
oracle.set(CreateArg{
.flags = tfSellNFToken,
.fee = baseFee,
.err = ter(temINVALID_FLAG)});
// Duplicate token pair
oracle.set(CreateArg{
.series = {{"XRP", "USD", 740, 1}, {"XRP", "USD", 750, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
// Price is not included
oracle.set(CreateArg{
.series =
{{"XRP", "USD", 740, 1}, {"XRP", "EUR", std::nullopt, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
// Token pair is in update and delete
oracle.set(CreateArg{
.series =
{{"XRP", "USD", 740, 1}, {"XRP", "USD", std::nullopt, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
// Token pair is in add and delete
oracle.set(CreateArg{
.series =
{{"XRP", "EUR", 740, 1}, {"XRP", "EUR", std::nullopt, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
// Array of token pair is 0 or exceeds 10
oracle.set(CreateArg{
.series =
{{"XRP", "US1", 740, 1},
{"XRP", "US2", 750, 1},
{"XRP", "US3", 740, 1},
{"XRP", "US4", 750, 1},
{"XRP", "US5", 740, 1},
{"XRP", "US6", 750, 1},
{"XRP", "US7", 740, 1},
{"XRP", "US8", 750, 1},
{"XRP", "US9", 740, 1},
{"XRP", "U10", 750, 1},
{"XRP", "U11", 740, 1}},
.fee = baseFee,
.err = ter(temARRAY_TOO_LARGE)});
oracle.set(CreateArg{
.series = {}, .fee = baseFee, .err = ter(temARRAY_EMPTY)});
}
// Array of token pair exceeds 10 after update
{
Env env{*this};
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(
env,
CreateArg{
.owner = owner,
.series = {{{"XRP", "USD", 740, 1}}},
.fee = baseFee});
oracle.set(UpdateArg{
.series =
{
{"XRP", "US1", 740, 1},
{"XRP", "US2", 750, 1},
{"XRP", "US3", 740, 1},
{"XRP", "US4", 750, 1},
{"XRP", "US5", 740, 1},
{"XRP", "US6", 750, 1},
{"XRP", "US7", 740, 1},
{"XRP", "US8", 750, 1},
{"XRP", "US9", 740, 1},
{"XRP", "U10", 750, 1},
},
.fee = baseFee,
.err = ter(tecARRAY_TOO_LARGE)});
}
{
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
// Asset class or provider not included on create
oracle.set(CreateArg{
.assetClass = std::nullopt,
.provider = "provider",
.fee = baseFee,
.err = ter(temMALFORMED)});
oracle.set(CreateArg{
.assetClass = "currency",
.provider = std::nullopt,
.uri = "URI",
.fee = baseFee,
.err = ter(temMALFORMED)});
// Asset class or provider are included on update
// and don't match the current values
oracle.set(CreateArg{
.fee = static_cast<int>(env.current()->fees().base.drops())});
BEAST_EXPECT(oracle.exists());
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.provider = "provider1",
.fee = baseFee,
.err = ter(temMALFORMED)});
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.assetClass = "currency1",
.fee = baseFee,
.err = ter(temMALFORMED)});
}
{
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
// Fields too long
// Asset class
std::string assetClass(17, '0');
oracle.set(CreateArg{
.assetClass = assetClass,
.fee = baseFee,
.err = ter(temMALFORMED)});
// provider
std::string const large(257, '0');
oracle.set(CreateArg{
.provider = large, .fee = baseFee, .err = ter(temMALFORMED)});
// URI
oracle.set(CreateArg{
.uri = large, .fee = baseFee, .err = ter(temMALFORMED)});
// Empty field
// Asset class
oracle.set(CreateArg{
.assetClass = "", .fee = baseFee, .err = ter(temMALFORMED)});
// provider
oracle.set(CreateArg{
.provider = "", .fee = baseFee, .err = ter(temMALFORMED)});
// URI
oracle.set(
CreateArg{.uri = "", .fee = baseFee, .err = ter(temMALFORMED)});
}
{
// Different owner creates a new object and fails because
// of missing fields currency/provider
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
Account const some("some");
env.fund(XRP(1'000), owner);
env.fund(XRP(1'000), some);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
oracle.set(UpdateArg{
.owner = some,
.series = {{"XRP", "USD", 740, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
}
{
// Invalid update time
using namespace std::chrono;
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
auto closeTime = [&]() {
return duration_cast<seconds>(
env.current()->info().closeTime.time_since_epoch() -
10'000s)
.count();
};
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
env.close(seconds(400));
// Less than the last close time - 300s
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.lastUpdateTime = static_cast<std::uint32_t>(closeTime() - 301),
.fee = baseFee,
.err = ter(tecINVALID_UPDATE_TIME)});
// Greater than last close time + 300s
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.lastUpdateTime = static_cast<std::uint32_t>(closeTime() + 311),
.fee = baseFee,
.err = ter(tecINVALID_UPDATE_TIME)});
oracle.set(
UpdateArg{.series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
BEAST_EXPECT(oracle.expectLastUpdateTime(
static_cast<std::uint32_t>(testStartTime.count() + 450)));
// Less than the previous lastUpdateTime
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.lastUpdateTime = static_cast<std::uint32_t>(449),
.fee = baseFee,
.err = ter(tecINVALID_UPDATE_TIME)});
// Less than the epoch time
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.lastUpdateTime = static_cast<int>(epoch_offset.count() - 1),
.fee = baseFee,
.err = ter(tecINVALID_UPDATE_TIME)});
}
{
// delete token pair that doesn't exist
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
oracle.set(UpdateArg{
.series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
.fee = baseFee,
.err = ter(tecTOKEN_PAIR_NOT_FOUND)});
// delete all token pairs
oracle.set(UpdateArg{
.series = {{"XRP", "USD", std::nullopt, std::nullopt}},
.fee = baseFee,
.err = ter(tecARRAY_EMPTY)});
}
{
// same BaseAsset and QuoteAsset
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(
env,
{.owner = owner,
.series = {{"USD", "USD", 740, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
}
{
// Scale is greater than maxPriceScale
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(
env,
{.owner = owner,
.series = {{"USD", "BTC", 740, maxPriceScale + 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
}
{
// Updating token pair to add and delete
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
oracle.set(UpdateArg{
.series =
{{"XRP", "EUR", std::nullopt, std::nullopt},
{"XRP", "EUR", 740, 1}},
.fee = baseFee,
.err = ter(temMALFORMED)});
// Delete token pair that doesn't exist in this oracle
oracle.set(UpdateArg{
.series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
.fee = baseFee,
.err = ter(tecTOKEN_PAIR_NOT_FOUND)});
// Delete token pair in oracle, which is not in the ledger
oracle.set(UpdateArg{
.documentID = 10,
.series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
.fee = baseFee,
.err = ter(temMALFORMED)});
}
{
// Bad fee
Env env(*this);
env.fund(XRP(1'000), owner);
Oracle oracle(
env, {.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
Oracle oracle1(
env,
{.owner = owner,
.fee = static_cast<int>(env.current()->fees().base.drops())});
oracle.set(
UpdateArg{.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
}
}
void
testCreate(FeatureBitset features)
{
testcase("Create");
using namespace jtx;
Account const owner("owner");
auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
auto const count = ownerCount(env, owner);
Oracle oracle(
env, {.owner = owner, .series = series, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
BEAST_EXPECT(ownerCount(env, owner) == (count + adj));
auto const entry = oracle.ledgerEntry();
BEAST_EXPECT(entry[jss::node][jss::Owner] == owner.human());
if (features[fixIncludeKeyletFields])
{
BEAST_EXPECT(
entry[jss::node][jss::OracleDocumentID] ==
oracle.documentID());
}
else
{
BEAST_EXPECT(!entry[jss::node].isMember(jss::OracleDocumentID));
}
BEAST_EXPECT(oracle.expectLastUpdateTime(946694810));
};
{
// owner count is adjusted by 1
Env env(*this, features);
test(env, {{"XRP", "USD", 740, 1}}, 1);
}
{
// owner count is adjusted by 2
Env env(*this, features);
test(
env,
{{"XRP", "USD", 740, 1},
{"BTC", "USD", 740, 1},
{"ETH", "USD", 740, 1},
{"CAN", "USD", 740, 1},
{"YAN", "USD", 740, 1},
{"GBP", "USD", 740, 1}},
2);
}
{
// Different owner creates a new object
Env env(*this, features);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
Account const some("some");
env.fund(XRP(1'000), owner);
env.fund(XRP(1'000), some);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
oracle.set(CreateArg{
.owner = some,
.series = {{"912810RR9", "USD", 740, 1}},
.fee = baseFee});
BEAST_EXPECT(Oracle::exists(env, some, oracle.documentID()));
}
}
void
testInvalidDelete()
{
testcase("Invalid Delete");
using namespace jtx;
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
Account const owner("owner");
env.fund(XRP(1'000), owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
{
// Invalid account
Account const bad("bad");
env.memoize(bad);
oracle.remove(
{.owner = bad,
.seq = seq(1),
.fee = baseFee,
.err = ter(terNO_ACCOUNT)});
}
// Invalid DocumentID
oracle.remove(
{.documentID = 2, .fee = baseFee, .err = ter(tecNO_ENTRY)});
// Invalid owner
Account const invalid("invalid");
env.fund(XRP(1'000), invalid);
oracle.remove(
{.owner = invalid, .fee = baseFee, .err = ter(tecNO_ENTRY)});
// Invalid flags
oracle.remove(
{.flags = tfSellNFToken,
.fee = baseFee,
.err = ter(temINVALID_FLAG)});
// Bad fee
oracle.remove({.fee = -1, .err = ter(temBAD_FEE)});
}
void
testDelete()
{
testcase("Delete");
using namespace jtx;
Account const owner("owner");
auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
Oracle oracle(
env, {.owner = owner, .series = series, .fee = baseFee});
auto const count = ownerCount(env, owner);
BEAST_EXPECT(oracle.exists());
oracle.remove({.fee = baseFee});
BEAST_EXPECT(!oracle.exists());
BEAST_EXPECT(ownerCount(env, owner) == (count - adj));
};
{
// owner count is adjusted by 1
Env env(*this);
test(env, {{"XRP", "USD", 740, 1}}, 1);
}
{
// owner count is adjusted by 2
Env env(*this);
test(
env,
{
{"XRP", "USD", 740, 1},
{"BTC", "USD", 740, 1},
{"ETH", "USD", 740, 1},
{"CAN", "USD", 740, 1},
{"YAN", "USD", 740, 1},
{"GBP", "USD", 740, 1},
},
2);
}
{
// deleting the account deletes the oracles
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
auto const alice = Account("alice");
auto const acctDelFee{drops(env.current()->fees().increment)};
env.fund(XRP(1'000), owner);
env.fund(XRP(1'000), alice);
Oracle oracle(
env,
{.owner = owner,
.series = {{"XRP", "USD", 740, 1}},
.fee = baseFee});
Oracle oracle1(
env,
{.owner = owner,
.documentID = 2,
.series = {{"XRP", "EUR", 740, 1}},
.fee = baseFee});
BEAST_EXPECT(ownerCount(env, owner) == 2);
BEAST_EXPECT(oracle.exists());
BEAST_EXPECT(oracle1.exists());
auto const index = env.closed()->seq();
auto const hash = env.closed()->info().hash;
for (int i = 0; i < 256; ++i)
env.close();
env(acctdelete(owner, alice), fee(acctDelFee));
env.close();
BEAST_EXPECT(!oracle.exists());
BEAST_EXPECT(!oracle1.exists());
// can still get the oracles via the ledger index or hash
auto verifyLedgerData = [&](auto const& field, auto const& value) {
Json::Value jvParams;
jvParams[field] = value;
jvParams[jss::binary] = false;
jvParams[jss::type] = jss::oracle;
Json::Value jrr =
env.rpc("json", "ledger_data", to_string(jvParams));
BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
};
verifyLedgerData(jss::ledger_index, index);
verifyLedgerData(jss::ledger_hash, to_string(hash));
}
}
void
testUpdate()
{
testcase("Update");
using namespace jtx;
Account const owner("owner");
{
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
auto count = ownerCount(env, owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
// update existing pair
oracle.set(
UpdateArg{.series = {{"XRP", "USD", 740, 2}}, .fee = baseFee});
BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 740, 2}}));
// owner count is increased by 1 since the oracle object is added
// with one token pair
count += 1;
BEAST_EXPECT(ownerCount(env, owner) == count);
// add new pairs, not-included pair is reset
oracle.set(
UpdateArg{.series = {{"XRP", "EUR", 700, 2}}, .fee = baseFee});
BEAST_EXPECT(oracle.expectPrice(
{{"XRP", "USD", 0, 0}, {"XRP", "EUR", 700, 2}}));
// owner count is not changed since the number of pairs is 2
BEAST_EXPECT(ownerCount(env, owner) == count);
// update both pairs
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}},
.fee = baseFee});
BEAST_EXPECT(oracle.expectPrice(
{{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}));
// owner count is not changed since the number of pairs is 2
BEAST_EXPECT(ownerCount(env, owner) == count);
// owner count is increased by 1 since the number of pairs is 6
oracle.set(UpdateArg{
.series =
{
{"BTC", "USD", 741, 2},
{"ETH", "EUR", 710, 2},
{"YAN", "EUR", 710, 2},
{"CAN", "EUR", 710, 2},
},
.fee = baseFee});
count += 1;
BEAST_EXPECT(ownerCount(env, owner) == count);
// update two pairs and delete four
oracle.set(UpdateArg{
.series = {{"BTC", "USD", std::nullopt, std::nullopt}},
.fee = baseFee});
oracle.set(UpdateArg{
.series =
{{"XRP", "USD", 742, 2},
{"XRP", "EUR", 711, 2},
{"ETH", "EUR", std::nullopt, std::nullopt},
{"YAN", "EUR", std::nullopt, std::nullopt},
{"CAN", "EUR", std::nullopt, std::nullopt}},
.fee = baseFee});
BEAST_EXPECT(oracle.expectPrice(
{{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}));
// owner count is decreased by 1 since the number of pairs is 2
count -= 1;
BEAST_EXPECT(ownerCount(env, owner) == count);
}
// Min reserve to create and update
{
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(
env.current()->fees().accountReserve(1) +
env.current()->fees().base * 2,
owner);
Oracle oracle(env, {.owner = owner, .fee = baseFee});
oracle.set(
UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
}
for (bool const withFixOrder : {false, true})
{
// Should be same order as creation
Env env(
*this,
withFixOrder ? testable_amendments()
: testable_amendments() - fixPriceOracleOrder);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
auto test = [&](Env& env, DataSeries const& series) {
env.fund(XRP(1'000), owner);
Oracle oracle(
env, {.owner = owner, .series = series, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
BEAST_EXPECT(
sle->getFieldArray(sfPriceDataSeries).size() ==
series.size());
auto const beforeQuoteAssetName1 =
sle->getFieldArray(sfPriceDataSeries)[0]
.getFieldCurrency(sfQuoteAsset)
.getText();
auto const beforeQuoteAssetName2 =
sle->getFieldArray(sfPriceDataSeries)[1]
.getFieldCurrency(sfQuoteAsset)
.getText();
oracle.set(UpdateArg{.series = series, .fee = baseFee});
sle = env.le(keylet::oracle(owner, oracle.documentID()));
auto const afterQuoteAssetName1 =
sle->getFieldArray(sfPriceDataSeries)[0]
.getFieldCurrency(sfQuoteAsset)
.getText();
auto const afterQuoteAssetName2 =
sle->getFieldArray(sfPriceDataSeries)[1]
.getFieldCurrency(sfQuoteAsset)
.getText();
if (env.current()->rules().enabled(fixPriceOracleOrder))
{
BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
}
else
{
BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
}
};
test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
}
}
void
testMultisig()
{
testcase("Multisig");
using namespace jtx;
Oracle::setFee(100'000);
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
Account const alice{"alice", KeyType::secp256k1};
Account const bogie{"bogie", KeyType::secp256k1};
Account const ed{"ed", KeyType::secp256k1};
Account const becky{"becky", KeyType::ed25519};
Account const zelda{"zelda", KeyType::secp256k1};
Account const bob{"bob", KeyType::secp256k1};
env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
// alice uses a regular key with the master disabled.
Account const alie{"alie", KeyType::secp256k1};
env(regkey(alice, alie));
env(fset(alice, asfDisableMaster), sig(alice));
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
env.close();
env.require(owners(alice, 1));
// Create
// Force close (true) and time advancement because the close time
// is no longer 0.
Oracle oracle(
env,
CreateArg{.owner = alice, .fee = baseFee, .close = true},
false);
oracle.set(CreateArg{
.msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
oracle.set(CreateArg{
.msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
BEAST_EXPECT(oracle.exists());
// Update
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.msig = msig(becky),
.fee = baseFee,
.err = ter(tefBAD_QUORUM)});
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.msig = msig(zelda),
.fee = baseFee,
.err = ter(tefBAD_SIGNATURE)});
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 741, 1}},
.msig = msig(becky, bogie),
.fee = baseFee});
BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
// remove the signer list
env(signers(alice, jtx::none), sig(alie));
env.close();
env.require(owners(alice, 1));
// create new signer list
env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
env.close();
// old list fails
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 740, 1}},
.msig = msig(becky, bogie),
.fee = baseFee,
.err = ter(tefBAD_SIGNATURE)});
// updated list succeeds
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 7412, 2}},
.msig = msig(zelda, bob),
.fee = baseFee});
BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
oracle.set(UpdateArg{
.series = {{"XRP", "USD", 74245, 3}},
.msig = msig(ed),
.fee = baseFee});
BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
// Remove
oracle.remove(
{.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
oracle.remove(
{.msig = msig(becky),
.fee = baseFee,
.err = ter(tefBAD_SIGNATURE)});
oracle.remove({.msig = msig(ed), .fee = baseFee});
BEAST_EXPECT(!oracle.exists());
}
void
testAmendment()
{
testcase("Amendment");
using namespace jtx;
auto const features = testable_amendments() - featurePriceOracle;
Account const owner("owner");
Env env(*this, features);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
env.fund(XRP(1'000), owner);
{
Oracle oracle(
env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
}
{
Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
}
}
public:
void
run() override
{
using namespace jtx;
auto const all = testable_amendments();
testInvalidSet();
testInvalidDelete();
testCreate(all);
testCreate(all - fixIncludeKeyletFields);
testDelete();
testUpdate();
testAmendment();
testMultisig();
}
};
BEAST_DEFINE_TESTSUITE(Oracle, app, ripple);
} // namespace oracle
} // namespace jtx
} // namespace test
} // namespace ripple