// make sure the deleted Account hasn't issued credit // make sure we aren't holding any credit // make sure the we delete all the offers // make sure the we delete all the trustlines // move the XLM to the new account bool MergeOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame::pointer otherAccount; Database& db = ledgerManager.getDatabase(); otherAccount = AccountFrame::loadAccount(mOperation.body.destination(), db); if (!otherAccount) { innerResult().code(ACCOUNT_MERGE_NO_ACCOUNT); return false; } if (TrustFrame::hasIssued(getSourceID(), db)) { innerResult().code(ACCOUNT_MERGE_CREDIT_HELD); return false; } std::vector<TrustFrame::pointer> lines; TrustFrame::loadLines(getSourceID(), lines, db); for(auto &l : lines) { if(l->getBalance() > 0) { innerResult().code(ACCOUNT_MERGE_HAS_CREDIT); return false; } } // delete offers std::vector<OfferFrame::pointer> offers; OfferFrame::loadOffers(getSourceID(), offers, db); for (auto& offer : offers) { offer->storeDelete(delta, db); } // delete trust lines for (auto& l : lines) { l->storeDelete(delta, db); } otherAccount->getAccount().balance += mSourceAccount->getAccount().balance; otherAccount->storeChange(delta, db); mSourceAccount->storeDelete(delta, db); innerResult().code(ACCOUNT_MERGE_SUCCESS); return true; }
bool MergeOpFrame::doCheckValid() { // makes sure not merging into self if (getSourceID() == mOperation.body.destination()) { innerResult().code(ACCOUNT_MERGE_MALFORMED); return false; } return true; }
bool ChangeTrustOpFrame::doCheckValid(medida::MetricsRegistry& metrics) { if (mChangeTrust.limit < 0) { metrics.NewMeter( {"op-change-trust", "invalid", "malformed-negative-limit"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_MALFORMED); return false; } if (!isAssetValid(mChangeTrust.line)) { metrics.NewMeter( {"op-change-trust", "invalid", "malformed-invalid-asset"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_MALFORMED); return false; } return true; }
bool AllowTrustOpFrame::doCheckValid() { if (mAllowTrust.currency.type() != CURRENCY_TYPE_ALPHANUM) { innerResult().code(ALLOW_TRUST_MALFORMED); return false; } Currency ci; ci.type(CURRENCY_TYPE_ALPHANUM); ci.alphaNum().currencyCode = mAllowTrust.currency.currencyCode(); ci.alphaNum().issuer = getSourceID(); if (!isCurrencyValid(ci)) { innerResult().code(ALLOW_TRUST_MALFORMED); return false; } return true; }
bool AllowTrustOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { if (!(mSourceAccount->getAccount().flags & AUTH_REQUIRED_FLAG)) { // this account doesn't require authorization to hold credit innerResult().code(ALLOW_TRUST_TRUST_NOT_REQUIRED); return false; } if (!(mSourceAccount->getAccount().flags & AUTH_REVOCABLE_FLAG) && !mAllowTrust.authorize) { innerResult().code(ALLOW_TRUST_CANT_REVOKE); return false; } Currency ci; ci.type(CURRENCY_TYPE_ALPHANUM); ci.alphaNum().currencyCode = mAllowTrust.currency.currencyCode(); ci.alphaNum().issuer = getSourceID(); Database& db = ledgerManager.getDatabase(); TrustFrame::pointer trustLine; trustLine = TrustFrame::loadTrustLine(mAllowTrust.trustor, ci, db); if (!trustLine) { innerResult().code(ALLOW_TRUST_NO_TRUST_LINE); return false; } innerResult().code(ALLOW_TRUST_SUCCESS); trustLine->setAuthorized(mAllowTrust.authorize); trustLine->storeChange(delta, db); return true; }
bool AllowTrustOpFrame::doCheckValid(Application& app) { if (mAllowTrust.asset.type() == ASSET_TYPE_NATIVE) { app.getMetrics() .NewMeter({"op-allow-trust", "invalid", "malformed-non-alphanum"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_MALFORMED); return false; } Asset ci; ci.type(mAllowTrust.asset.type()); if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM4) { ci.alphaNum4().assetCode = mAllowTrust.asset.assetCode4(); ci.alphaNum4().issuer = getSourceID(); } else if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM12) { ci.alphaNum12().assetCode = mAllowTrust.asset.assetCode12(); ci.alphaNum12().issuer = getSourceID(); } if (!isAssetValid(ci)) { app.getMetrics() .NewMeter({"op-allow-trust", "invalid", "malformed-invalid-asset"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_MALFORMED); return false; } return true; }
bool MergeOpFrame::doCheckValid(Application& app, uint32_t ledgerVersion) { // makes sure not merging into self if (getSourceID() == mOperation.body.destination()) { app.getMetrics() .NewMeter({"op-merge", "invalid", "malformed-self-merge"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_MALFORMED); return false; } return true; }
bool ChangeTrustOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); auto tlI = TrustFrame::loadTrustLineIssuer(getSourceID(), mChangeTrust.line, db, delta); auto& trustLine = tlI.first; auto& issuer = tlI.second; if (trustLine) { // we are modifying an old trustline if (mChangeTrust.limit < trustLine->getBalance()) { // Can't drop the limit below the balance you are holding with them metrics.NewMeter({"op-change-trust", "failure", "invalid-limit"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_INVALID_LIMIT); return false; } if (mChangeTrust.limit == 0) { // line gets deleted trustLine->storeDelete(delta, db); mSourceAccount->addNumEntries(-1, ledgerManager); mSourceAccount->storeChange(delta, db); } else { if (!issuer) { metrics.NewMeter({"op-change-trust", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_NO_ISSUER); return false; } trustLine->getTrustLine().limit = mChangeTrust.limit; trustLine->storeChange(delta, db); } metrics.NewMeter({"op-change-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(CHANGE_TRUST_SUCCESS); return true; } else { // new trust line if (mChangeTrust.limit == 0) { metrics.NewMeter({"op-change-trust", "failure", "invalid-limit"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_INVALID_LIMIT); return false; } if (!issuer) { metrics.NewMeter({"op-change-trust", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_NO_ISSUER); return false; } trustLine = std::make_shared<TrustFrame>(); auto& tl = trustLine->getTrustLine(); tl.accountID = getSourceID(); tl.asset = mChangeTrust.line; tl.limit = mChangeTrust.limit; tl.balance = 0; trustLine->setAuthorized(!issuer->isAuthRequired()); if (!mSourceAccount->addNumEntries(1, ledgerManager)) { metrics.NewMeter({"op-change-trust", "failure", "low-reserve"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_LOW_RESERVE); return false; } mSourceAccount->storeChange(delta, db); trustLine->storeAdd(delta, db); metrics.NewMeter({"op-change-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(CHANGE_TRUST_SUCCESS); return true; } }
bool InflationOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { LedgerDelta inflationDelta(delta); auto& lcl = inflationDelta.getHeader(); time_t closeTime = lcl.scpValue.closeTime; uint32_t seq = lcl.inflationSeq; time_t inflationTime = (INFLATION_START_TIME + seq * INFLATION_FREQUENCY); if (closeTime < inflationTime) { metrics.NewMeter({"op-inflation", "failure", "not-time"}, "operation") .Mark(); innerResult().code(INFLATION_NOT_TIME); return false; } /* Inflation is calculated using the following 1. calculate tally of votes based on "inflationDest" set on each account 2. take the top accounts (by vote) that get at least .05% of the vote 3. If no accounts are over this threshold then the extra goes back to the inflation pool */ int64_t totalVotes = lcl.totalCoins; int64_t minBalance = bigDivide(totalVotes, INFLATION_WIN_MIN_PERCENT, TRILLION); std::vector<AccountFrame::InflationVotes> winners; auto& db = ledgerManager.getDatabase(); AccountFrame::processForInflation( [&](AccountFrame::InflationVotes const& votes) { if (votes.mVotes >= minBalance) { winners.push_back(votes); return true; } return false; }, INFLATION_NUM_WINNERS, db); int64 amountToDole = bigDivide(lcl.totalCoins, INFLATION_RATE_TRILLIONTHS, TRILLION); amountToDole += lcl.feePool; lcl.feePool = 0; lcl.inflationSeq++; // now credit each account innerResult().code(INFLATION_SUCCESS); auto& payouts = innerResult().payouts(); int64 leftAfterDole = amountToDole; for (auto const& w : winners) { AccountFrame::pointer winner; int64 toDoleThisWinner = bigDivide(amountToDole, w.mVotes, totalVotes); if (toDoleThisWinner == 0) continue; winner = AccountFrame::loadAccount(w.mInflationDest, db); if (winner) { leftAfterDole -= toDoleThisWinner; lcl.totalCoins += toDoleThisWinner; winner->getAccount().balance += toDoleThisWinner; winner->storeChange(inflationDelta, db); payouts.emplace_back(w.mInflationDest, toDoleThisWinner); } } // put back in fee pool as unclaimed funds lcl.feePool += leftAfterDole; inflationDelta.commit(); metrics.NewMeter({"op-inflation", "success", "apply"}, "operation").Mark(); return true; }
bool SetOptionsOpFrame::doApply(AbstractLedgerTxn& ltx) { auto header = ltx.loadHeader(); auto sourceAccount = loadSourceAccount(ltx, header); auto& account = sourceAccount.current().data.account(); if (mSetOptions.inflationDest) { AccountID inflationID = *mSetOptions.inflationDest; if (!(inflationID == getSourceID())) { if (!stellar::loadAccountWithoutRecord(ltx, inflationID)) { innerResult().code(SET_OPTIONS_INVALID_INFLATION); return false; } } account.inflationDest.activate() = inflationID; } if (mSetOptions.clearFlags) { if ((*mSetOptions.clearFlags & allAccountAuthFlags) && isImmutableAuth(sourceAccount)) { innerResult().code(SET_OPTIONS_CANT_CHANGE); return false; } account.flags = account.flags & ~*mSetOptions.clearFlags; } if (mSetOptions.setFlags) { if ((*mSetOptions.setFlags & allAccountAuthFlags) && isImmutableAuth(sourceAccount)) { innerResult().code(SET_OPTIONS_CANT_CHANGE); return false; } account.flags = account.flags | *mSetOptions.setFlags; } if (mSetOptions.homeDomain) { account.homeDomain = *mSetOptions.homeDomain; } if (mSetOptions.masterWeight) { account.thresholds[THRESHOLD_MASTER_WEIGHT] = *mSetOptions.masterWeight & UINT8_MAX; } if (mSetOptions.lowThreshold) { account.thresholds[THRESHOLD_LOW] = *mSetOptions.lowThreshold & UINT8_MAX; } if (mSetOptions.medThreshold) { account.thresholds[THRESHOLD_MED] = *mSetOptions.medThreshold & UINT8_MAX; } if (mSetOptions.highThreshold) { account.thresholds[THRESHOLD_HIGH] = *mSetOptions.highThreshold & UINT8_MAX; } if (mSetOptions.signer) { auto& signers = account.signers; if (mSetOptions.signer->weight) { // add or change signer bool found = false; for (auto& oldSigner : signers) { if (oldSigner.key == mSetOptions.signer->key) { oldSigner.weight = mSetOptions.signer->weight; found = true; } } if (!found) { if (signers.size() == signers.max_size()) { innerResult().code(SET_OPTIONS_TOO_MANY_SIGNERS); return false; } switch (addNumEntries(header, sourceAccount, 1)) { case AddSubentryResult::SUCCESS: break; case AddSubentryResult::LOW_RESERVE: innerResult().code(SET_OPTIONS_LOW_RESERVE); return false; case AddSubentryResult::TOO_MANY_SUBENTRIES: mResult.code(opTOO_MANY_SUBENTRIES); return false; default: throw std::runtime_error( "Unexpected result from addNumEntries"); } signers.push_back(*mSetOptions.signer); } } else { // delete signer auto it = signers.begin(); while (it != signers.end()) { Signer& oldSigner = *it; if (oldSigner.key == mSetOptions.signer->key) { it = signers.erase(it); addNumEntries(header, sourceAccount, -1); } else { it++; } } } normalizeSigners(sourceAccount); } innerResult().code(SET_OPTIONS_SUCCESS); return true; }
bool SetOptionsOpFrame::doCheckValid(uint32_t ledgerVersion) { if (mSetOptions.setFlags) { if (*mSetOptions.setFlags & ~allAccountFlags) { innerResult().code(SET_OPTIONS_UNKNOWN_FLAG); return false; } } if (mSetOptions.clearFlags) { if (*mSetOptions.clearFlags & ~allAccountFlags) { innerResult().code(SET_OPTIONS_UNKNOWN_FLAG); return false; } } if (mSetOptions.setFlags && mSetOptions.clearFlags) { if ((*mSetOptions.setFlags & *mSetOptions.clearFlags) != 0) { innerResult().code(SET_OPTIONS_BAD_FLAGS); return false; } } if (mSetOptions.masterWeight) { if (*mSetOptions.masterWeight > UINT8_MAX) { innerResult().code(SET_OPTIONS_THRESHOLD_OUT_OF_RANGE); return false; } } if (mSetOptions.lowThreshold) { if (*mSetOptions.lowThreshold > UINT8_MAX) { innerResult().code(SET_OPTIONS_THRESHOLD_OUT_OF_RANGE); return false; } } if (mSetOptions.medThreshold) { if (*mSetOptions.medThreshold > UINT8_MAX) { innerResult().code(SET_OPTIONS_THRESHOLD_OUT_OF_RANGE); return false; } } if (mSetOptions.highThreshold) { if (*mSetOptions.highThreshold > UINT8_MAX) { innerResult().code(SET_OPTIONS_THRESHOLD_OUT_OF_RANGE); return false; } } if (mSetOptions.signer) { auto isSelf = mSetOptions.signer->key == KeyUtils::convertKey<SignerKey>(getSourceID()); auto isPublicKey = KeyUtils::canConvert<PublicKey>(mSetOptions.signer->key); if (isSelf || (!isPublicKey && ledgerVersion < 3)) { innerResult().code(SET_OPTIONS_BAD_SIGNER); return false; } if (mSetOptions.signer->weight > UINT8_MAX && ledgerVersion > 9) { innerResult().code(SET_OPTIONS_BAD_SIGNER); return false; } } if (mSetOptions.homeDomain) { if (!isString32Valid(*mSetOptions.homeDomain)) { innerResult().code(SET_OPTIONS_INVALID_HOME_DOMAIN); return false; } } return true; }
bool InflationOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { LedgerDelta inflationDelta(delta); auto& lcl = inflationDelta.getHeader(); time_t closeTime = lcl.scpValue.closeTime; uint32_t seq = lcl.inflationSeq; time_t inflationTime = (INFLATION_START_TIME + seq * INFLATION_FREQUENCY); if (closeTime < inflationTime) { metrics.NewMeter({"op-inflation", "failure", "not-time"}, "operation") .Mark(); innerResult().code(INFLATION_NOT_TIME); return false; } /* Inflation is calculated using the following 1. calculate tally of votes based on "inflationDest" set on each account 2. take the top accounts (by vote) that exceed 1.5% (INFLATION_WIN_MIN_PERCENT) of votes, up to 50 accounts (INFLATION_NUM_WINNERS) exception: if no account crosses the INFLATION_WIN_MIN_PERCENT, the top 50 is used 3. share the coins between those accounts proportionally to the number of votes they got. */ int64_t totalVotes = 0; bool first = true; int64_t minBalance = bigDivide(lcl.totalCoins, INFLATION_WIN_MIN_PERCENT, TRILLION); std::vector<AccountFrame::InflationVotes> winners; auto& db = ledgerManager.getDatabase(); AccountFrame::processForInflation( [&](AccountFrame::InflationVotes const& votes) { if (first && votes.mVotes < minBalance) { // need to take the entire set if nobody crossed the threshold minBalance = 0; } first = false; bool res; if (votes.mVotes >= minBalance) { totalVotes += votes.mVotes; winners.push_back(votes); res = true; } else { res = false; } return res; }, INFLATION_NUM_WINNERS, db); int64 amountToDole = bigDivide(lcl.totalCoins, INFLATION_RATE_TRILLIONTHS, TRILLION); amountToDole += lcl.feePool; lcl.feePool = 0; lcl.inflationSeq++; // now credit each account innerResult().code(INFLATION_SUCCESS); auto& payouts = innerResult().payouts(); if (totalVotes != 0) { for (auto const& w : winners) { AccountFrame::pointer winner; int64 toDoleThisWinner = bigDivide(amountToDole, w.mVotes, totalVotes); if (toDoleThisWinner == 0) continue; winner = AccountFrame::loadAccount(w.mInflationDest, db); if (winner) { lcl.totalCoins += toDoleThisWinner; winner->getAccount().balance += toDoleThisWinner; winner->storeChange(inflationDelta, db); payouts.emplace_back(w.mInflationDest, toDoleThisWinner); } else { // put back in fee pool as unclaimed funds lcl.feePool += toDoleThisWinner; } } } else { // put back in fee pool as unclaimed funds lcl.feePool += amountToDole; } inflationDelta.commit(); metrics.NewMeter({"op-inflation", "success", "apply"}, "operation").Mark(); return true; }
// make sure the deleted Account hasn't issued credit // make sure we aren't holding any credit // make sure the we delete all the offers // make sure the we delete all the trustlines // move the XLM to the new account bool MergeOpFrame::doApply(Application& app, AbstractLedgerState& ls) { auto header = ls.loadHeader(); auto otherAccount = stellar::loadAccount(ls, mOperation.body.destination()); if (!otherAccount) { app.getMetrics() .NewMeter({"op-merge", "failure", "no-account"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_NO_ACCOUNT); return false; } int64_t sourceBalance = 0; if (header.current().ledgerVersion > 4 && header.current().ledgerVersion < 8) { // in versions < 8, merge account could be called with a stale account LedgerKey key(ACCOUNT); key.account().accountID = getSourceID(); auto thisAccount = ls.loadWithoutRecord(key); if (!thisAccount) { app.getMetrics() .NewMeter({"op-merge", "failure", "no-account"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_NO_ACCOUNT); return false; } if (header.current().ledgerVersion > 5) { sourceBalance = thisAccount.current().data.account().balance; } } auto sourceAccountEntry = loadSourceAccount(ls, header); auto const& sourceAccount = sourceAccountEntry.current().data.account(); // Only set sourceBalance here if it wasn't set in the previous block if (header.current().ledgerVersion <= 5 || header.current().ledgerVersion >= 8) { sourceBalance = sourceAccount.balance; } if (isImmutableAuth(sourceAccountEntry)) { app.getMetrics() .NewMeter({"op-merge", "failure", "static-auth"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_IMMUTABLE_SET); return false; } if (sourceAccount.numSubEntries != sourceAccount.signers.size()) { app.getMetrics() .NewMeter({"op-merge", "failure", "has-sub-entries"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_HAS_SUB_ENTRIES); return false; } if (header.current().ledgerVersion >= 10) { SequenceNumber maxSeq = getStartingSequenceNumber(header); // don't allow the account to be merged if recreating it would cause it // to jump backwards if (sourceAccount.seqNum >= maxSeq) { app.getMetrics() .NewMeter({"op-merge", "failure", "too-far"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_SEQNUM_TOO_FAR); return false; } } // "success" path starts if (!addBalance(header, otherAccount, sourceBalance)) { app.getMetrics() .NewMeter({"op-merge", "failure", "dest-full"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_DEST_FULL); return false; } sourceAccountEntry.erase(); app.getMetrics() .NewMeter({"op-merge", "success", "apply"}, "operation") .Mark(); innerResult().code(ACCOUNT_MERGE_SUCCESS); innerResult().sourceAccountBalance() = sourceBalance; return true; }
bool AllowTrustOpFrame::doApply(Application& app, LedgerDelta& delta, LedgerManager& ledgerManager) { if (ledgerManager.getCurrentLedgerVersion() > 2) { if (mAllowTrust.trustor == getSourceID()) { // since version 3 it is not // allowed to use ALLOW_TRUST on // self app.getMetrics() .NewMeter({"op-allow-trust", "failure", "trust-self"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_SELF_NOT_ALLOWED); return false; } } if (!(mSourceAccount->getAccount().flags & AUTH_REQUIRED_FLAG)) { // this account doesn't require authorization to // hold credit app.getMetrics() .NewMeter({"op-allow-trust", "failure", "not-required"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_TRUST_NOT_REQUIRED); return false; } if (!(mSourceAccount->getAccount().flags & AUTH_REVOCABLE_FLAG) && !mAllowTrust.authorize) { app.getMetrics() .NewMeter({"op-allow-trust", "failure", "cant-revoke"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_CANT_REVOKE); return false; } Asset ci; ci.type(mAllowTrust.asset.type()); if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM4) { ci.alphaNum4().assetCode = mAllowTrust.asset.assetCode4(); ci.alphaNum4().issuer = getSourceID(); } else if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM12) { ci.alphaNum12().assetCode = mAllowTrust.asset.assetCode12(); ci.alphaNum12().issuer = getSourceID(); } Database& db = ledgerManager.getDatabase(); TrustFrame::pointer trustLine; trustLine = TrustFrame::loadTrustLine(mAllowTrust.trustor, ci, db, &delta); if (!trustLine) { app.getMetrics() .NewMeter({"op-allow-trust", "failure", "no-trust-line"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_NO_TRUST_LINE); return false; } app.getMetrics() .NewMeter({"op-allow-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_SUCCESS); trustLine->setAuthorized(mAllowTrust.authorize); trustLine->storeChange(delta, db); return true; }