Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auction bid rpc and consensus implementation #698

Merged
merged 1 commit into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ std::string ToString(CustomTxType type) {
case CustomTxType::Vault: return "Vault";
case CustomTxType::UpdateVault: return "UpdateVault";
case CustomTxType::DepositToVault: return "DepositToVault";
case CustomTxType::AuctionBid: return "AuctionBid";
case CustomTxType::None: return "None";
}
return "None";
Expand Down Expand Up @@ -149,6 +150,7 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) {
case CustomTxType::Vault: return CVaultMessage{};
case CustomTxType::UpdateVault: return CUpdateVaultMessage{};
case CustomTxType::DepositToVault: return CDepositToVaultMessage{};
case CustomTxType::AuctionBid: return CAuctionBidMessage{};
case CustomTxType::None: return CCustomTxMessageNone{};
}
return CCustomTxMessageNone{};
Expand Down Expand Up @@ -455,6 +457,11 @@ class CCustomMetadataParseVisitor : public boost::static_visitor<Res>
return !res ? res : serialize(obj);
}

Res operator()(CAuctionBidMessage& obj) const {
auto res = isPostFortCanningFork();
return !res ? res : serialize(obj);
}

Res operator()(CCustomTxMessageNone&) const {
return Res::Ok();
}
Expand Down Expand Up @@ -2150,6 +2157,50 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
return Res::Ok();
}

Res operator()(const CAuctionBidMessage& obj) const {
// owner auth
if (!HasAuth(obj.from)) {
return Res::Err("tx must have at least one input from token owner");
}
// vault exists
auto vault = mnview.GetVault(obj.vaultId);
if (!vault)
return Res::Err("Cannot find existing vault with id %s", obj.vaultId.GetHex());

// vault under liquidation
if (!vault.val->isUnderLiquidation)
return Res::Err("Cannot bid to vault which is not under liquidation");

auto data = mnview.GetAuction(obj.vaultId, height);
if (!data)
return Res::Err("No auction data to vault %s", obj.vaultId.GetHex());

auto batch = mnview.GetAuctionBatch(obj.vaultId, obj.index);
if (!batch)
return Res::Err("No batch to vault/index %s/%d", obj.vaultId.GetHex(), obj.index);

if (obj.amount.nTokenId != batch->loanAmount.nTokenId)
return Res::Err("Bid token does not match auction one");

auto bid = mnview.GetAuctionBid(obj.vaultId, obj.index);
if (!bid) {
auto amount = MultiplyAmounts(batch->loanAmount.nValue, COIN + data->liquidationPenalty);
if (amount > obj.amount.nValue)
return Res::Err("First bid should include liquidation penalty of %d%%", data->liquidationPenalty * 100 / COIN);
} else {
auto amount = MultiplyAmounts(bid->second.nValue, COIN + (COIN / 100));
if (amount > obj.amount.nValue)
return Res::Err("Bid override should be at least 1%% higher than current one");
// immediate refund previous bid
CalculateOwnerRewards(bid->first);
mnview.AddBalance(bid->first, bid->second);
}
//check balance
CalculateOwnerRewards(obj.from);
auto res = mnview.SubBalance(obj.from, obj.amount);
return !res ? res : mnview.StoreAuctionBid(obj.vaultId, obj.index, {obj.from, obj.amount});
}

Res operator()(const CCustomTxMessageNone&) const {
return Res::Ok();
}
Expand Down
7 changes: 5 additions & 2 deletions src/masternodes/mn_checks.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ enum class CustomTxType : uint8_t
DestroyLoanScheme = 'D',
Vault = 'V',
UpdateVault = 'v',
DepositToVault = 'S'
DepositToVault = 'S',
AuctionBid = 'I'
};

inline CustomTxType CustomTxCodeToType(uint8_t ch) {
Expand Down Expand Up @@ -126,6 +127,7 @@ inline CustomTxType CustomTxCodeToType(uint8_t ch) {
case CustomTxType::Vault:
case CustomTxType::UpdateVault:
case CustomTxType::DepositToVault:
case CustomTxType::AuctionBid:
case CustomTxType::None:
return type;
}
Expand Down Expand Up @@ -282,7 +284,8 @@ typedef boost::variant<
CDestroyLoanSchemeMessage,
CVaultMessage,
CUpdateVaultMessage,
CDepositToVaultMessage
CDepositToVaultMessage,
CAuctionBidMessage
> CCustomTxMessage;

CCustomTxMessage customTypeToMessage(CustomTxType txType);
Expand Down
8 changes: 8 additions & 0 deletions src/masternodes/rpc_customtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ class CCustomTxRpcVisitor : public boost::static_visitor<void>
rpcInfo.pushKV("from", ScriptToString(obj.from));
rpcInfo.pushKV("amount", obj.amount.ToString());
}

void operator()(const CAuctionBidMessage& obj) const {
rpcInfo.pushKV("vaultid", obj.vaultId.GetHex());
rpcInfo.pushKV("index", int64_t(obj.index));
rpcInfo.pushKV("from", ScriptToString(obj.from));
rpcInfo.pushKV("amount", obj.amount.ToString());
}

void operator()(const CCustomTxMessageNone&) const {
}
};
Expand Down
91 changes: 91 additions & 0 deletions src/masternodes/rpc_vault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,96 @@ UniValue deposittovault(const JSONRPCRequest& request) {
return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
}

UniValue auctionbid(const JSONRPCRequest& request) {
CWallet *const pwallet = GetWallet(request);

RPCHelpMan{"auctionbid",
"Bid to vault in auction\n" +
HelpRequiringPassphrase(pwallet) + "\n",
{
{"vaultid", RPCArg::Type::STR, RPCArg::Optional::NO, "Vault id"},
{"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "Auction index"},
{"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get tokens"},
{"amount", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount of amount@symbol format"},
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
},
},
},
}
},
RPCResult{
"\"txid\" (string) The transaction id.\n"
},
RPCExamples{
HelpExampleCli("auctionbid",
"84b22eee1964768304e624c416f29a91d78a01dc5e8e12db26bdac0670c67bb2i 0 mwSDMvn1Hoc8DsoB7AkLv7nxdrf5Ja4jsF 100@TSLA") +
HelpExampleRpc("deposittovault",
"84b22eee1964768304e624c416f29a91d78a01dc5e8e12db26bdac0670c67bb2i 0 mwSDMvn1Hoc8DsoB7AkLv7nxdrf5Ja4jsF 1@DTSLA")
},
}.Check(request);

RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VNUM, UniValue::VSTR, UniValue::VSTR, UniValue::VARR},
false);

if (pwallet->chain().isInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot make auction bid while still in Initial Block Download");

pwallet->BlockUntilSyncedToCurrentChain();
LockedCoinsScopedGuard lcGuard(pwallet);

// decode vaultid
CVaultId vaultId = ParseHashV(request.params[0], "vaultid");
uint32_t index = request.params[1].get_int();
auto from = DecodeScript(request.params[2].get_str());
CTokenAmount amount = DecodeAmount(pwallet->chain(), request.params[3].get_str(), "amount");

CAuctionBidMessage msg{vaultId, index, from, amount};
CDataStream markedMetadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION);
markedMetadata << static_cast<unsigned char>(CustomTxType::DepositToVault)
<< msg;
CScript scriptMeta;
scriptMeta << OP_RETURN << ToByteVector(markedMetadata);

int targetHeight = chainHeight(*pwallet->chain().lock()) + 1;

const auto txVersion = GetTransactionVersion(targetHeight);
CMutableTransaction rawTx(txVersion);

rawTx.vout.push_back(CTxOut(0, scriptMeta));

CTransactionRef optAuthTx;
std::set<CScript> auths{from};
rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[4]);

CCoinControl coinControl;

// Set change to from address
CTxDestination dest;
ExtractDestination(from, dest);
if (IsValidDestination(dest)) {
coinControl.destChange = dest;
}

fund(rawTx, pwallet, optAuthTx, &coinControl);

// check execution
{
LOCK(cs_main);
CCoinsViewCache coins(&::ChainstateActive().CoinsTip());
if (optAuthTx)
AddCoins(coins, *optAuthTx, targetHeight);
auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg});
execTestTx(CTransaction(rawTx), targetHeight, metadata, CAuctionBidMessage{}, coins);
}
return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
}

static const CRPCCommand commands[] =
{
// category name actor (function) params
Expand All @@ -465,6 +555,7 @@ static const CRPCCommand commands[] =
{"vault", "getvault", &getvault, {"id"}},
{"vault", "updatevault", &updatevault, {"id", "parameters", "inputs"}},
{"vault", "deposittovault", &deposittovault, {"id", "from", "amount", "inputs"}},
{"vault", "auctionbid", &auctionbid, {"id", "index", "from", "amount", "inputs"}},
};

void RegisterVaultRPCCommands(CRPCTable& tableRPC) {
Expand Down
18 changes: 13 additions & 5 deletions src/masternodes/vault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ Res CVaultView::EraseAuction(const CVaultId& vaultId, uint32_t height)
return Res::Err("Auction for vault <%s> not found", vaultId.GetHex());
}

boost::optional<CAuctionData> CVaultView::GetAuction(const CVaultId& vaultId, uint32_t height)
{
auto it = LowerBound<AuctionHeightKey>(std::make_pair(height, vaultId));
for (; it.Valid(); it.Next()) {
if (it.Key().second == vaultId) {
return it.Value().as<CAuctionData>();
}
}
return {};
}

Res CVaultView::StoreAuctionBatch(const CVaultId& vaultId, uint32_t id, const CAuctionBatch& batch)
{
WriteBy<AuctionBatchKey>(std::make_pair(vaultId, id), batch);
Expand All @@ -121,13 +132,10 @@ boost::optional<CAuctionBatch> CVaultView::GetAuctionBatch(const CVaultId& vault
return ReadBy<AuctionBatchKey, CAuctionBatch>(std::make_pair(vaultId, id));
}

void CVaultView::ForEachVaultAuction(std::function<bool(const CVaultId&, const CAuctionData&)> callback, uint32_t height)
void CVaultView::ForEachVaultAuction(std::function<bool(const CVaultId&, uint32_t, const CAuctionData&)> callback, uint32_t height)
prasannavl marked this conversation as resolved.
Show resolved Hide resolved
{
ForEach<AuctionHeightKey, std::pair<uint32_t, CVaultId>, CAuctionData>([&](const std::pair<uint32_t, CVaultId>& pair, const CAuctionData& data) {
if (pair.first != height) {
return false;
}
return callback(pair.second, data);
return callback(pair.second, pair.first, data);
}, std::make_pair(height, CVaultId{}));
}

Expand Down
21 changes: 20 additions & 1 deletion src/masternodes/vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ struct CDepositToVaultMessage {
}
};

struct CAuctionBidMessage {
CVaultId vaultId;
uint32_t index;
CScript from;
CTokenAmount amount;

ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(vaultId);
READWRITE(index);
READWRITE(from);
READWRITE(amount);
}
};

struct CAuctionData {
uint32_t batchCount;
CAmount liquidationPenalty;
Expand Down Expand Up @@ -103,10 +121,11 @@ class CVaultView : public virtual CStorageView

Res StoreAuction(const CVaultId& vaultId, uint32_t height, const CAuctionData& data);
Res EraseAuction(const CVaultId& vaultId, uint32_t height);
boost::optional<CAuctionData> GetAuction(const CVaultId& vaultId, uint32_t height);
Res StoreAuctionBatch(const CVaultId& vaultId, uint32_t id, const CAuctionBatch& batch);
Res EraseAuctionBatch(const CVaultId& vaultId, uint32_t id);
boost::optional<CAuctionBatch> GetAuctionBatch(const CVaultId& vaultId, uint32_t id);
void ForEachVaultAuction(std::function<bool(const CVaultId&, const CAuctionData&)> callback, uint32_t height);
void ForEachVaultAuction(std::function<bool(const CVaultId&, uint32_t, const CAuctionData&)> callback, uint32_t height = 0);
prasannavl marked this conversation as resolved.
Show resolved Hide resolved

using COwnerTokenAmount = std::pair<CScript, CTokenAmount>;
Res StoreAuctionBid(const CVaultId& vaultId, uint32_t id, COwnerTokenAmount amount);
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "updatevault", 1, "parameters" },
{ "updatevault", 2, "inputs" },
{ "deposittovault", 3, "inputs" },
{ "auctionbid", 4, "inputs" },

{ "spv_sendrawtx", 0, "rawtx" },
{ "spv_createanchor", 0, "inputs" },
Expand Down
5 changes: 4 additions & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2982,7 +2982,10 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
return true;
});
}
cache.ForEachVaultAuction([&](const CVaultId& vaultId, const CAuctionData& data) {
cache.ForEachVaultAuction([&](const CVaultId& vaultId, uint32_t height, const CAuctionData& data) {
if (int(height) != pindex->nHeight) {
return false;
}
std::set<DCT_ID> tokensLooseInterest;
for (uint32_t i = 0; i < data.batchCount; i++) {
auto batch = cache.GetAuctionBatch(vaultId, i);
Expand Down