Skip to content
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
27 changes: 19 additions & 8 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1193,14 +1193,7 @@ bool CTransaction::CheckContracts(const MapPrevTx& inputs) const
return DoS(100, error("%s: contract in non-standard tx", __func__));
}

const auto is_valid_burn_output = [](const CTxOut& output) {
return output.scriptPubKey[0] == OP_RETURN
&& output.nValue >= NN::Contract::BURN_AMOUNT;
};

if (std::none_of(vout.begin(), vout.end(), is_valid_burn_output)) {
return DoS(100, error("%s: no sufficient burn output", __func__));
}
int64_t required_burn_fee = 0;

for (const auto& contract : GetContracts()) {
if (contract.m_version <= 1) {
Expand All @@ -1216,6 +1209,24 @@ bool CTransaction::CheckContracts(const MapPrevTx& inputs) const
if (contract.RequiresMasterKey() && !HasMasterKeyInput(inputs)) {
return DoS(100, error("%s: contract requires master key", __func__));
}

required_burn_fee += contract.RequiredBurnAmount();
}

int64_t supplied_burn_fee = 0;

for (const auto& output : vout) {
if (output.scriptPubKey[0] == OP_RETURN) {
supplied_burn_fee += output.nValue;
}
}

if (supplied_burn_fee < required_burn_fee) {
return DoS(100, error(
"%s: insufficient burn output. Required: %s, supplied: %s",
__func__,
FormatMoney(required_burn_fee),
FormatMoney(supplied_burn_fee)));
}

return true;
Expand Down
10 changes: 10 additions & 0 deletions src/neuralnet/beacon.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,16 @@ class BeaconPayload : public IContractPayload
return m_beacon.ToString();
}

//!
//! \brief Get the burn fee amount required to send a particular contract.
//!
//! \return Burn fee in units of 1/100000000 GRC.
//!
int64_t RequiredBurnAmount() const override
{
return 0.5 * COIN;
}

//!
//! \brief Sign the beacon with its private key.
//!
Expand Down
17 changes: 16 additions & 1 deletion src/neuralnet/contract/contract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class EmptyPayload : public IContractPayload
return "";
}

int64_t RequiredBurnAmount() const override
{
return MAX_MONEY;
}

ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
Expand Down Expand Up @@ -110,6 +115,11 @@ class LegacyPayload : public IContractPayload
return m_value;
}

int64_t RequiredBurnAmount() const override
{
return Contract::STANDARD_BURN_AMOUNT;
}

ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
Expand Down Expand Up @@ -383,7 +393,7 @@ void NN::RevertContracts(const std::vector<Contract>& contracts)
// Class: Contract
// -----------------------------------------------------------------------------

constexpr int64_t Contract::BURN_AMOUNT; // for clang
constexpr int64_t Contract::STANDARD_BURN_AMOUNT; // for clang

Contract::Contract()
: m_version(Contract::CURRENT_VERSION)
Expand Down Expand Up @@ -568,6 +578,11 @@ const CPubKey& Contract::ResolvePublicKey() const
return m_public_key.Key();
}

int64_t Contract::RequiredBurnAmount() const
{
return m_body.m_payload->RequiredBurnAmount();
}

bool Contract::WellFormed() const
{
return m_version > 0 && m_version <= Contract::CURRENT_VERSION
Expand Down
9 changes: 8 additions & 1 deletion src/neuralnet/contract/contract.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class Contract
//! \brief The amount of coin set for a burn output in a transaction that
//! broadcasts a contract in units of 1/100000000 GRC.
//!
static constexpr int64_t BURN_AMOUNT = 0.5 * COIN;
static constexpr int64_t STANDARD_BURN_AMOUNT = 0.5 * COIN;

//!
//! \brief A contract type from a transaction message.
Expand Down Expand Up @@ -563,6 +563,13 @@ class Contract
//!
const CPubKey& ResolvePublicKey() const;

//!
//! \brief Get the burn fee amount required to send a particular contract.
//!
//! \return Burn fee in units of 1/100000000 GRC.
//!
int64_t RequiredBurnAmount() const;

//!
//! \brief Determine whether the instance represents a complete contract.
//!
Expand Down
25 changes: 17 additions & 8 deletions src/neuralnet/contract/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ bool SelectMasterInputOutput(CCoinControl& coin_control)
//!
//! \param wtx_new A new transaction with a contract.
//! \param reserve_key Key reserved for any change.
//! \param admin \c true for an administrative contract.
//! \param burn_fee Total burn fee required for contracts in the transaction.
//!
//! \return \c true if coin selection succeeded.
//!
bool CreateContractTx(CWalletTx& wtx_out, CReserveKey reserve_key, bool admin)
bool CreateContractTx(CWalletTx& wtx_out, CReserveKey reserve_key, int64_t burn_fee)
{
CCoinControl coin_control_out;
int64_t applied_fee_out; // Unused
bool admin = false;

for (const auto& contract : wtx_out.vContracts) {
admin |= contract.RequiresMasterKey();
}

// Configure inputs/outputs for the address associated with the master key.
// Nodes validate administrative contracts by checking that the containing
Expand All @@ -79,7 +84,7 @@ bool CreateContractTx(CWalletTx& wtx_out, CReserveKey reserve_key, bool admin)
scriptPubKey << OP_RETURN;

return pwalletMain->CreateTransaction(
{ std::make_pair(std::move(scriptPubKey), Contract::BURN_AMOUNT) },
{ std::make_pair(std::move(scriptPubKey), burn_fee) },
wtx_out,
reserve_key,
applied_fee_out,
Expand All @@ -90,12 +95,11 @@ bool CreateContractTx(CWalletTx& wtx_out, CReserveKey reserve_key, bool admin)
//! \brief Send a transaction that contains a contract.
//!
//! \param wtx_new A new transaction with a contract.
//! \param admin \c true for an administrative contract.
//!
//! \return An empty string when successful or a description of the error that
//! occurred. TODO: Refactor to remove string-based signaling.
//!
std::string SendContractTx(CWalletTx& wtx_new, const bool admin)
std::string SendContractTx(CWalletTx& wtx_new)
{
CReserveKey reserve_key(pwalletMain);

Expand All @@ -112,14 +116,19 @@ std::string SendContractTx(CWalletTx& wtx_new, const bool admin)
}

int64_t balance = pwalletMain->GetBalance();
int64_t burn_fee = 0;

for (const auto& contract : wtx_new.vContracts) {
burn_fee += contract.RequiredBurnAmount();
}

if (balance < COIN || balance < Contract::BURN_AMOUNT + nTransactionFee) {
if (balance < COIN || balance < burn_fee + nTransactionFee) {
std::string strError = _("Balance too low to create a contract.");
LogPrintf("%s: %s", __func__, strError);
return strError;
}

if (!CreateContractTx(wtx_new, reserve_key, admin)) {
if (!CreateContractTx(wtx_new, reserve_key, burn_fee)) {
std::string strError = _("Error: Transaction creation failed.");
LogPrintf("%s: %s", __func__, strError);
return strError;
Expand Down Expand Up @@ -175,7 +184,7 @@ std::pair<CWalletTx, std::string> NN::SendContract(Contract contract)

wtx.vContracts.emplace_back(std::move(contract));

std::string error = SendContractTx(wtx, contract.RequiresMasterKey());
std::string error = SendContractTx(wtx);

return std::make_pair(std::move(wtx), std::move(error));
}
7 changes: 7 additions & 0 deletions src/neuralnet/contract/payload.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ class IContractPayload
//!
virtual std::string LegacyValueString() const = 0;

//!
//! \brief Get the burn fee amount required to send a particular contract.
//!
//! \return Burn fee in units of 1/100000000 GRC.
//!
virtual int64_t RequiredBurnAmount() const = 0;

//!
//! \brief Serialize the contract to the provided file.
//!
Expand Down
10 changes: 10 additions & 0 deletions src/neuralnet/project.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ class Project : public IContractPayload
return m_url;
}

//!
//! \brief Get the burn fee amount required to send a particular contract.
//!
//! \return Burn fee in units of 1/100000000 GRC.
//!
int64_t RequiredBurnAmount() const override
{
return 0.5 * COIN; // TODO: reduce fee for admin contracts?
}

//!
//! \brief Get a user-friendly display name created from the project key.
//!
Expand Down
1 change: 1 addition & 0 deletions src/test/neuralnet/beacon_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ BOOST_AUTO_TEST_CASE(it_behaves_like_a_contract_payload)
BOOST_CHECK(payload.WellFormed(NN::ContractAction::ADD) == true);
BOOST_CHECK(payload.LegacyKeyString() == cpid.ToString());
BOOST_CHECK(payload.LegacyValueString() == payload.m_beacon.ToString());
BOOST_CHECK(payload.RequiredBurnAmount() > 0);
}

BOOST_AUTO_TEST_CASE(it_checks_whether_the_payload_is_well_formed_for_add)
Expand Down
12 changes: 12 additions & 0 deletions src/test/neuralnet/contract_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class TestPayload : public NN::IContractPayload
return m_data;
}

int64_t RequiredBurnAmount() const override
{
return NN::Contract::STANDARD_BURN_AMOUNT;
}

ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
Expand Down Expand Up @@ -942,6 +947,13 @@ BOOST_AUTO_TEST_CASE(it_determines_whether_a_legacy_v1_contract_is_valid)
BOOST_CHECK(contract.Validate() == false);
}

BOOST_AUTO_TEST_CASE(it_determines_the_requred_burn_fee)
{
const NN::Contract contract = TestMessage::Current();

BOOST_CHECK(contract.RequiredBurnAmount() > 0);
}

BOOST_AUTO_TEST_CASE(it_provides_access_to_the_contract_payload)
{
const NN::Contract contract = TestMessage::Current();
Expand Down
1 change: 1 addition & 0 deletions src/test/neuralnet/project_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ BOOST_AUTO_TEST_CASE(it_behaves_like_a_contract_payload)
BOOST_CHECK(project.WellFormed(NN::ContractAction::ADD) == true);
BOOST_CHECK(project.LegacyKeyString() == "Enigma");
BOOST_CHECK(project.LegacyValueString() == "http://enigma.test/@");
BOOST_CHECK(project.RequiredBurnAmount() > 0);
}

BOOST_AUTO_TEST_CASE(it_checks_whether_the_payload_is_well_formed_for_add)
Expand Down