bool OTOffer::MakeOffer( bool bBuyingOrSelling, // True == SELLING, False == BUYING const int64_t& lPriceLimit, // Per Minimum Increment... (Zero price means // it's a market order.) const int64_t& lTotalAssetsOffer, // Total assets available for sale or // purchase. const int64_t& lMinimumIncrement, // The minimum increment that must be // bought or sold for each transaction const int64_t& lTransactionNum, // The transaction number authorizing this // trade. const time64_t& VALID_FROM, // defaults to RIGHT NOW const time64_t& VALID_TO) // defaults to 24 hours (a "Day Order") { m_bSelling = bBuyingOrSelling; // Bid or Ask? SetTransactionNum(lTransactionNum); SetTotalAssetsOnOffer(lTotalAssetsOffer); // 500 bushels for sale. m_strContractType.Set((m_bSelling ? "ASK" : "BID")); // Make sure minimum increment isn't bigger than total Assets. // (If you pass them into this function as the same value, it's functionally // a "FILL OR KILL" order.) int64_t lRealMinInc = lMinimumIncrement; if (lMinimumIncrement > lTotalAssetsOffer) // Once the total, minus finish // so far, is smaller than the // minimum increment, lRealMinInc = lTotalAssetsOffer; // then the OTTrade object I am linked // to will expire and remove me from // the market. // OR it could set the minimum increment to the remainder. But then need to // calc price. SetMinimumIncrement(lRealMinInc); // Must sell in 50 bushel increments. // (Perhaps on the 10-bushel market it // will sell in 5 increments of 10.) SetPriceLimit(lPriceLimit); // Won't sell for any less than $10 per // increment. (Always get best market price.) SetFinishedSoFar(0); // So far have already sold 350 bushels. Actual amount // available is (total - finished). time64_t REAL_VALID_FROM = VALID_FROM; time64_t REAL_VALID_TO = VALID_TO; if (OT_TIME_ZERO >= VALID_FROM) { REAL_VALID_FROM = OTTimeGetCurrentTime(); // This time is set to TODAY NOW } if (OT_TIME_ZERO >= VALID_TO) { // (All offers default to a "DAY ORDER" if valid dates not specified.) REAL_VALID_TO = OTTimeAddTimeInterval( REAL_VALID_FROM, OTTimeGetSecondsFromTime(OT_TIME_DAY_IN_SECONDS)); // 1 day. } SetValidFrom(REAL_VALID_FROM); SetValidTo(REAL_VALID_TO); return true; }
// Verify whether the CURRENT date is WITHIN the VALID FROM / TO dates. bool Instrument::VerifyCurrentDate() { const time64_t CURRENT_TIME = OTTimeGetCurrentTime(); if ((CURRENT_TIME >= m_VALID_FROM) && ((CURRENT_TIME <= m_VALID_TO) || (OT_TIME_ZERO == m_VALID_TO))) return true; else return false; }
// This is called by the client side. First you call MakeOffer() to set up the // Offer, // then you call IssueTrade() and pass the Offer into it here. bool OTTrade::IssueTrade(OTOffer& offer, char stopSign, int64_t stopPrice) { // Make sure the Stop Sign is within parameters (0, '<', or '>') if ((stopSign == 0) || (stopSign == '<') || (stopSign == '>')) stopSign_ = stopSign; else { otErr << "Bad data in Stop Sign while issuing trade: " << stopSign << "\n"; return false; } // Make sure, if this IS a Stop order, that the price is within parameters // and set. if ((stopSign_ == '<') || (stopSign_ == '>')) { if (0 >= stopPrice) { otErr << "Expected Stop Price for trade.\n"; return false; } stopPrice_ = stopPrice; } tradesAlreadyDone_ = 0; SetCreationDate( OTTimeGetCurrentTime()); // This time is set to TODAY NOW (OTCronItem) // Validate the Notary ID, Instrument Definition ID, Currency Type ID, and // Date Range. if ((GetNotaryID() != offer.GetNotaryID()) || (GetCurrencyID() != offer.GetCurrencyID()) || (GetInstrumentDefinitionID() != offer.GetInstrumentDefinitionID()) || (offer.GetValidFrom() < OT_TIME_ZERO) || (offer.GetValidTo() < offer.GetValidFrom())) { return false; } // currencyTypeID_ // This is already set in the constructors of this // and the offer. (And compared.) // currencyAcctID_ // This is already set in the constructor of this. // Set the (now validated) date range as per the Offer. SetValidFrom(offer.GetValidFrom()); SetValidTo(offer.GetValidTo()); // Get the transaction number from the Offer. SetTransactionNum(offer.GetTransactionNum()); // Save a copy of the offer, in XML form, here on this Trade. String strOffer(offer); marketOffer_.Set(strOffer); return true; }
// Verify whether the CURRENT date is AFTER the the VALID TO date. // Notice, this will return false, if the instrument is NOT YET VALID. // You have to use VerifyCurrentDate() to make sure you're within the // valid date range to use this instrument. But sometimes you only want // to know if it's expired, regardless of whether it's valid yet. So this // function answers that for you. bool Instrument::IsExpired() { const time64_t CURRENT_TIME = OTTimeGetCurrentTime(); // If the current time is AFTER the valid-TO date, // AND the valid_to is a nonzero number (0 means "doesn't expire") // THEN return true (it's expired.) // if ((CURRENT_TIME >= m_VALID_TO) && (m_VALID_TO > OT_TIME_ZERO)) return true; else return false; }
// PayDividendVisitor::Trigger() is used in // OTUnitDefinition::VisitAccountRecords() // cppcheck-suppress unusedFunction bool PayDividendVisitor::Trigger( const Account& theSharesAccount) // theSharesAccount // is, say, a Pepsi // shares // account. Here, we'll send a dollars voucher // to its owner. { const std::int64_t lPayoutAmount = (theSharesAccount.GetBalance() * GetPayoutPerShare()); if (lPayoutAmount <= 0) { otOut << "PayDividendVisitor::Trigger: nothing to pay, " "since this account owns no shares. (Returning " "true.)"; return true; // nothing to pay, since this account owns no shares. // Success! } OT_ASSERT(false == GetNotaryID()->empty()); const auto theNotaryID = GetNotaryID(); OT_ASSERT(!GetPayoutUnitTypeId()->empty()); const Identifier& payoutUnitTypeId_ = (Identifier::Factory()); OT_ASSERT(!GetVoucherAcctID()->empty()); const Identifier& theVoucherAcctID = (GetVoucherAcctID()); Nym& theServerNym = const_cast<Nym&>(server_.GetServerNym()); const auto theServerNymID = Identifier::Factory(theServerNym); const Identifier& RECIPIENT_ID = theSharesAccount.GetNymID(); OT_ASSERT(!GetNymID()->empty()); const Identifier& theSenderNymID = (GetNymID()); OT_ASSERT(!GetMemo()->empty()); const String& strMemo = (GetMemo()); // Note: theSenderNymID is the originator of the Dividend Payout. // However, all the actual vouchers will be from "the server Nym" and // not from theSenderNymID. So then why is it even here? Because anytime // there's an error, the server will send to theSenderNymID instead of // RECIPIENT_ID (so the original sender can have his money back, instead of // just having it get lost in the ether.) bool bReturnValue = false; auto theVoucher{ server_.API().Factory().Cheque(theNotaryID, Identifier::Factory())}; OT_ASSERT(false != bool(theVoucher)); // 10 minutes == 600 Seconds // 1 hour == 3600 Seconds // 1 day == 86400 Seconds // 30 days == 2592000 Seconds // 3 months == 7776000 Seconds // 6 months == 15552000 Seconds const time64_t VALID_FROM = OTTimeGetCurrentTime(); // This time is set to TODAY NOW const time64_t VALID_TO = OTTimeAddTimeInterval( VALID_FROM, OTTimeGetSecondsFromTime(OT_TIME_SIX_MONTHS_IN_SECONDS)); // This time // occurs in // 180 days (6 months). // Todo hardcoding. TransactionNumber lNewTransactionNumber = 0; auto context = server_.API().Wallet().mutable_ClientContext( theServerNym.ID(), theServerNym.ID()); bool bGotNextTransNum = server_.GetTransactor().issueNextTransactionNumberToNym( context.It(), lNewTransactionNumber); // We save the transaction // number on the server Nym (normally we'd discard it) because // when the cheque is deposited, the server nym, as the owner of // the voucher account, needs to verify the transaction # on the // cheque (to prevent double-spending of cheques.) if (bGotNextTransNum) { const bool bIssueVoucher = theVoucher->IssueCheque( lPayoutAmount, // The amount of the cheque. lNewTransactionNumber, // Requiring a transaction number prevents // double-spending of cheques. VALID_FROM, // The expiration date (valid from/to dates) of the // cheque VALID_TO, // Vouchers are automatically starting today and lasting // 6 // months. theVoucherAcctID, // The asset account the cheque is drawn on. theServerNymID, // Nym ID of the sender (in this case the server // nym.) strMemo, // Optional memo field. Includes item note and request // memo. Identifier::Factory(RECIPIENT_ID)); // All account crediting / debiting happens in the caller, in // server::Server. // (AND it happens only ONCE, to cover ALL vouchers.) // Then in here, the voucher either gets send to the recipient, or if // error, sent back home to // the issuer Nym. (ALL the funds are removed, then the vouchers are // sent one way or the other.) // Any returned vouchers, obviously serve to notify the dividend payer // of where the errors were // (as well as give him the opportunity to get his money back.) // bool bSent = false; if (bIssueVoucher) { // All this does is set the voucher's internal contract string to // "VOUCHER" instead of "CHEQUE". We also set the server itself as // the remitter, which is unusual for vouchers, but necessary in the // case of dividends. // theVoucher->SetAsVoucher(theServerNymID, theVoucherAcctID); theVoucher->SignContract(theServerNym); theVoucher->SaveContract(); // Send the voucher to the payments inbox of the recipient. // const auto strVoucher = String::Factory(*theVoucher); auto thePayment{server_.API().Factory().Payment(strVoucher)}; OT_ASSERT(false != bool(thePayment)); // calls DropMessageToNymbox bSent = server_.SendInstrumentToNym( theNotaryID, theServerNymID, // sender nym RECIPIENT_ID, // recipient nym *thePayment, "payDividend"); // todo: hardcoding. bReturnValue = bSent; // <======= RETURN VALUE. if (bSent) m_lAmountPaidOut += lPayoutAmount; // At the end of iterating all accounts, if // m_lAmountPaidOut is less than // lTotalPayoutAmount, then we return to rest // to the sender. } else { const auto strPayoutUnitTypeId = String::Factory( Identifier::Factory(payoutUnitTypeId_)), strRecipientNymID = String::Factory(RECIPIENT_ID); otErr << "PayDividendVisitor::Trigger: ERROR failed issuing " << "voucher (to send to dividend payout recipient.) WAS " << "TRYING TO PAY " << lPayoutAmount << " of instrument definition " << strPayoutUnitTypeId << " to Nym " << strRecipientNymID << ".\n"; } // If we didn't send it, then we need to return the funds to where they // came from. // if (!bSent) { auto theReturnVoucher{server_.API().Factory().Cheque( theNotaryID, Identifier::Factory())}; OT_ASSERT(false != bool(theReturnVoucher)); const bool bIssueReturnVoucher = theReturnVoucher->IssueCheque( lPayoutAmount, // The amount of the cheque. lNewTransactionNumber, // Requiring a transaction number // prevents double-spending of cheques. VALID_FROM, // The expiration date (valid from/to dates) of the // cheque VALID_TO, // Vouchers are automatically starting today and // lasting 6 months. theVoucherAcctID, // The asset account the cheque is drawn on. theServerNymID, // Nym ID of the sender (in this case the // server nym.) strMemo, // Optional memo field. Includes item note and request // memo. Identifier::Factory(theSenderNymID)); // We're returning the // money to its original // sender. if (bIssueReturnVoucher) { // All this does is set the voucher's internal contract string // to // "VOUCHER" instead of "CHEQUE". // theReturnVoucher->SetAsVoucher( theServerNymID, theVoucherAcctID); theReturnVoucher->SignContract(theServerNym); theReturnVoucher->SaveContract(); // Return the voucher back to the payments inbox of the original // sender. // const auto strReturnVoucher = String::Factory(*theReturnVoucher); auto theReturnPayment{ server_.API().Factory().Payment(strReturnVoucher)}; OT_ASSERT(false != bool(theReturnPayment)); // calls DropMessageToNymbox bSent = server_.SendInstrumentToNym( theNotaryID, theServerNymID, // sender nym theSenderNymID, // recipient nym (original sender.) *theReturnPayment, "payDividend"); // todo: hardcoding. if (bSent) m_lAmountReturned += lPayoutAmount; // At the end of iterating all accounts, // if m_lAmountPaidOut+m_lAmountReturned // is less than lTotalPayoutAmount, then // we return the rest to the sender. } else { const auto strPayoutUnitTypeId = String::Factory(payoutUnitTypeId_), strSenderNymID = String::Factory(theSenderNymID); otErr << "PayDividendVisitor::Trigger: ERROR " "failed issuing voucher (to return back to " "the dividend payout initiator, after a failed " "payment attempt to the originally intended " "recipient.) WAS TRYING TO PAY " << lPayoutAmount << " of instrument definition " << strPayoutUnitTypeId << " to Nym " << strSenderNymID << ".\n"; } } // if !bSent } else // !bGotNextTransNum { const auto strPayoutUnitTypeId = String::Factory(payoutUnitTypeId_), strRecipientNymID = String::Factory(RECIPIENT_ID); otErr << OT_METHOD << __FUNCTION__ << ": ERROR!! Failed issuing next transaction number while " << "trying to send a voucher (while paying dividends.) " << "WAS TRYING TO PAY " << lPayoutAmount << " of instrument definition " << strPayoutUnitTypeId->Get() << " to Nym " << strRecipientNymID->Get() << ".\n"; } return bReturnValue; }
// OTCron calls this regularly, which is my chance to expire, etc. // Return True if I should stay on the Cron list for more processing. // Return False if I should be removed and deleted. bool OTTrade::ProcessCron() { // Right now Cron is called 10 times per second. // I'm going to slow down all trades so they are once every // GetProcessInterval() if (GetLastProcessDate() > OT_TIME_ZERO) { // (Default ProcessInterval is 1 second, but Trades will use 10 seconds, // and Payment Plans will use an hour or day.) if (OTTimeGetTimeInterval(OTTimeGetCurrentTime(), GetLastProcessDate()) <= GetProcessInterval()) return true; } // Keep a record of the last time this was processed. // (NOT saved to storage, only used while the software is running.) // (Thus no need to release signatures, sign contract, save contract, etc.) SetLastProcessDate(OTTimeGetCurrentTime()); // PAST END DATE? // First call the parent's version (which this overrides) so it has // a chance to check its stuff. Currently it checks IsExpired(). if (!ot_super::ProcessCron()) return false; // It's expired or flagged for removal--remove it from // Cron. // You might ask, why not check here if this trade is flagged for removal? // Supposedly the answer is, because it's only below that I have the market // pointer, // and am able to remove the corresponding trade from the market. // Therefore I am adding a hook for "onRemoval" so that Objects such as // OTTrade ALWAYS // have the opportunity to perform such cleanup, without having to juggle // such logic. // REACHED START DATE? // Okay, so it's not expired. But might not have reached START DATE yet... if (!VerifyCurrentDate()) return true; // The Trade is not yet valid, so we return. BUT, we return // true, so it will stay on Cron until it BECOMES valid. // TRADE-specific stuff below. bool bStayOnMarket = true; // by default stay on the market (until some rule expires me.) Identifier OFFER_MARKET_ID; OTMarket* market = nullptr; // If the Offer is already active on a market, then I already have a pointer // to // it. This function returns that pointer. If nullptr, it tries to find the // offer on // the market and then sets the pointer and returns. If it can't find it, IT // TRIES // TO ADD IT TO THE MARKET and sets the pointer and returns it. OTOffer* offer = GetOffer( &OFFER_MARKET_ID, &market); // Both of these parameters are optional. // In this case, the offer is NOT on the market. // Perhaps it wasn't ready to activate yet. if (offer == nullptr) { // The offer SHOULD HAVE been on the market, since we're within the // valid range, // and GetOffer adds it when it's not already there. // otErr << "OTTrade::ProcessCron: Offer SHOULD have been on // Market. I might ASSERT this.\n"; // comment this out // Actually! If it's a Stop Order, then it WOULD be within the valid // range, yet would // not yet have activated. So I don't want to log some big error every // time a stop order // checks its prices. } else if (market == nullptr) { // todo. (This will already leave a log above in GetOffer somewhere.) // otErr << "OTTrade::ProcessCron: Market was nullptr.\n"; // // comment this out } else // If a valid pointer was returned, that means the offer is on the // market. { // Make sure it hasn't already been flagged by someone else... if (IsFlaggedForRemoval()) // This is checked above in // OTCronItem::ProcessCron(). bStayOnMarket = false; // I'm leaving the check here in case the // flag was set since then. else // Process it! <=================== { otInfo << "Processing trade: " << GetTransactionNum() << ".\n"; bStayOnMarket = market->ProcessTrade(*this, *offer); // No need to save the Trade or Offer, since they will // be saved inside this call if they are changed. } } // Return True if I should stay on the Cron list for more processing. // Return False if I should be removed and deleted. return bStayOnMarket; // defaults true, so if false, that means someone is // removing it for a reason. }