Skip to content

Commit

Permalink
Mint tokens to any address (#1650)
Browse files Browse the repository at this point in the history
* Allow minting to any address

* Update RPC to optionally use OBJ

* Keep previous CMintTokensMessage format

* Run CalculateOwnerRewards only for recipient

* Make CMintTokensMessage.to serialisation optional

* Fix RPCHelpMan formatting

* Add fork check

* Update feature_consortium test

* Reject transactions below fork height, validate to address

* Reject TX if invalid to address provided

* Feature gate mint tokens to address

* Check attributes with assert

* Remove unused variable

Co-authored-by: Peter Bushnell <[email protected]>
  • Loading branch information
shohamc1 and Bushstar authored Dec 23, 2022
1 parent b2e574c commit d60f1bf
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 24 deletions.
10 changes: 5 additions & 5 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class CMainParams : public CChainParams {
consensus.FortCanningGreatWorldHeight = 2212000; // Sep 7th, 2022.
consensus.FortCanningEpilogueHeight = 2257500; // Sep 22nd, 2022.
consensus.GrandCentralHeight = 2479000; // Dec 8th, 2022.
consensus.GrandCentralNextHeight = std::numeric_limits<int>::max();
consensus.GrandCentralEpilogueHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -395,7 +395,7 @@ class CTestNetParams : public CChainParams {
consensus.FortCanningGreatWorldHeight = 1223000;
consensus.FortCanningEpilogueHeight = 1244000;
consensus.GrandCentralHeight = 1366000;
consensus.GrandCentralNextHeight = std::numeric_limits<int>::max();
consensus.GrandCentralEpilogueHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -607,7 +607,7 @@ class CDevNetParams : public CChainParams {
consensus.FortCanningGreatWorldHeight = 1223000;
consensus.FortCanningEpilogueHeight = 1244000;
consensus.GrandCentralHeight = 1366000;
consensus.GrandCentralNextHeight = std::numeric_limits<int>::max();
consensus.GrandCentralEpilogueHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks
Expand Down Expand Up @@ -820,7 +820,7 @@ class CRegTestParams : public CChainParams {
consensus.FortCanningGreatWorldHeight = 10000000;
consensus.FortCanningEpilogueHeight = 10000000;
consensus.GrandCentralHeight = 10000000;
consensus.GrandCentralNextHeight = 10000000;
consensus.GrandCentralEpilogueHeight = 10000000;

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -1062,7 +1062,7 @@ void SetupCommonArgActivationParams(Consensus::Params &consensus) {
UpdateHeightValidation("Fort Canning Great World", "-greatworldheight", consensus.FortCanningGreatWorldHeight);
UpdateHeightValidation("Fort Canning Epilogue", "-fortcanningepilogueheight", consensus.FortCanningEpilogueHeight);
UpdateHeightValidation("Grand Central", "-grandcentralheight", consensus.GrandCentralHeight);
UpdateHeightValidation("Grand Central Next", "-grandcentralnextheight", consensus.GrandCentralNextHeight);
UpdateHeightValidation("Grand Central Next", "-grandcentralepilogueheight", consensus.GrandCentralEpilogueHeight);

if (gArgs.GetBoolArg("-simulatemainnet", false)) {
consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks
Expand Down
2 changes: 1 addition & 1 deletion src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct Params {
int FortCanningGreatWorldHeight;
int FortCanningEpilogueHeight;
int GrandCentralHeight;
int GrandCentralNextHeight;
int GrandCentralEpilogueHeight;

/** Foundation share after AMK, normalized to COIN = 100% */
CAmount foundationShareDFIP1;
Expand Down
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ void SetupServerArgs()
gArgs.AddArg("-greatworldheight", "Alias for Fort Canning Great World fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-fortcanningepilogueheight", "Alias for Fort Canning Epilogue fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-grandcentralheight", "Grand Central fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-grandcentralnextheight", "Grand Central Next fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-grandcentralepilogueheight", "Grand Central Next fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
gArgs.AddArg("-regtest-skip-loan-collateral-validation", "Skip loan collateral check for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
gArgs.AddArg("-regtest-minttoken-simulate-mainnet", "Simulate mainnet for minttokens on regtest - default behavior on regtest is to allow anyone to mint mintable tokens for ease of testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
Expand Down
11 changes: 9 additions & 2 deletions src/masternodes/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const std::map<uint8_t, std::map<std::string, uint8_t>> &ATTRIBUTES::allowedKeys
{"members", DFIPKeys::Members},
{"gov-payout", DFIPKeys::CFPPayout},
{"emission-unused-fund", DFIPKeys::EmissionUnusedFund},
{"mint-tokens-to-address", DFIPKeys::MintTokens},
}},
{AttributeTypes::Governance,
{
Expand Down Expand Up @@ -264,6 +265,7 @@ const std::map<uint8_t, std::map<uint8_t, std::string>> &ATTRIBUTES::displayKeys
{DFIPKeys::Members, "members"},
{DFIPKeys::CFPPayout, "gov-payout"},
{DFIPKeys::EmissionUnusedFund, "emission-unused-fund"},
{DFIPKeys::MintTokens, "mint-tokens-to-address"},
}},
{AttributeTypes::Live,
{
Expand Down Expand Up @@ -585,6 +587,7 @@ const std::map<uint8_t, std::map<uint8_t, std::function<ResVal<CAttributeValue>(
{DFIPKeys::ConsortiumEnabled, VerifyBool},
{DFIPKeys::CFPPayout, VerifyBool},
{DFIPKeys::EmissionUnusedFund, VerifyBool},
{DFIPKeys::MintTokens, VerifyBool},
}},
{AttributeTypes::Locks,
{
Expand Down Expand Up @@ -802,7 +805,7 @@ Res ATTRIBUTES::ProcessVariable(const std::string &key,
typeKey != DFIPKeys::MNSetRewardAddress && typeKey != DFIPKeys::MNSetOperatorAddress &&
typeKey != DFIPKeys::MNSetOwnerAddress && typeKey != DFIPKeys::GovernanceEnabled &&
typeKey != DFIPKeys::ConsortiumEnabled && typeKey != DFIPKeys::CFPPayout &&
typeKey != DFIPKeys::EmissionUnusedFund) {
typeKey != DFIPKeys::EmissionUnusedFund && typeKey != DFIPKeys::MintTokens) {
return Res::Err("Unsupported type for Feature {%d}", typeKey);
}
} else if (typeId == ParamIDs::Foundation) {
Expand Down Expand Up @@ -1502,7 +1505,11 @@ Res ATTRIBUTES::Validate(const CCustomCSView &view) const {
break;

case AttributeTypes::Param:
if (attrV0->typeId == ParamIDs::Feature || attrV0->typeId == ParamIDs::Foundation ||
if (attrV0->typeId == ParamIDs::Feature && attrV0->key == DFIPKeys::MintTokens) {
if (view.GetLastHeight() < Params().GetConsensus().GrandCentralEpilogueHeight) {
return Res::Err("Cannot be set before GrandCentralEpilogueHeight");
}
} else if (attrV0->typeId == ParamIDs::Feature || attrV0->typeId == ParamIDs::Foundation ||
attrV0->key == DFIPKeys::Members) {
if (view.GetLastHeight() < Params().GetConsensus().GrandCentralHeight) {
return Res::Err("Cannot be set before GrandCentralHeight");
Expand Down
1 change: 1 addition & 0 deletions src/masternodes/govvariables/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ enum DFIPKeys : uint8_t {
GovernanceEnabled = 'q',
CFPPayout = 'r',
EmissionUnusedFund = 's',
MintTokens = 't',
};

enum GovernanceKeys : uint8_t {
Expand Down
21 changes: 19 additions & 2 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,14 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor {
const auto fortCanningCrunchHeight = static_cast<uint32_t>(consensus.FortCanningCrunchHeight);
const auto grandCentralHeight = static_cast<uint32_t>(consensus.GrandCentralHeight);

CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::MintTokens};
const auto attributes = mnview.GetAttributes();
assert(attributes);
const auto toAddressEnabled = attributes->GetValue(enabledKey, false);

if (!toAddressEnabled && !obj.to.empty())
return Res::Err("Mint tokens to address is not enabled");

// check auth and increase balance of token's owner
for (const auto &[tokenId, amount] : obj.balances) {
if (Params().NetworkIDString() == CBaseChainParams::MAIN && height >= fortCanningCrunchHeight &&
Expand All @@ -1431,8 +1439,17 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor {
if (!minted)
return minted;

CalculateOwnerRewards(*mintable.val);
auto res = mnview.AddBalance(*mintable.val, CTokenAmount{tokenId, amount});
CScript mintTo{*mintable.val};
if (!obj.to.empty()) {
CTxDestination destination;
if (ExtractDestination(obj.to, destination) && IsValidDestination(destination))
mintTo = obj.to;
else
return Res::Err("Invalid \'to\' address provided");
}

CalculateOwnerRewards(mintTo);
auto res = mnview.AddBalance(mintTo, CTokenAmount{tokenId, amount});
if (!res)
return res;

Expand Down
5 changes: 5 additions & 0 deletions src/masternodes/mn_checks.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,16 @@ struct CUpdateTokenMessage {

struct CMintTokensMessage : public CBalances {
using CBalances::CBalances;
CScript to;

ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
READWRITEAS(CBalances, *this);

if (!s.eof()) {
READWRITE(to);
}
}
};

Expand Down
5 changes: 4 additions & 1 deletion src/masternodes/rpc_customtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ class CCustomTxRpcVisitor {

void operator()(const CUpdateTokenMessage &obj) const { tokenInfo(obj.token); }

void operator()(const CMintTokensMessage &obj) const { rpcInfo.pushKVs(tokenBalances(obj)); }
void operator()(const CMintTokensMessage &obj) const {
rpcInfo.pushKVs(tokenBalances(obj));
rpcInfo.pushKV("to", ScriptToString(obj.to));
}

void operator()(const CBurnTokensMessage &obj) const {
rpcInfo.pushKVs(tokenBalances(obj.amounts));
Expand Down
40 changes: 34 additions & 6 deletions src/masternodes/rpc_tokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,15 +658,20 @@ UniValue minttokens(const JSONRPCRequest& request) {
},
},
},
{"to", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Address to mint tokens to"
},
},
RPCResult{
"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"
},
RPCExamples{
HelpExampleCli("minttokens", "10@symbol")
+ HelpExampleCli("minttokens",
"10@symbol '[{\"txid\":\"id\",\"vout\":0}]'")
+ HelpExampleRpc("minttokens", "10@symbol '[{\"txid\":\"id\",\"vout\":0}]'")
R"(10@symbol '[{"txid":"id","vout":0}]')")
+ HelpExampleCli("minttokens",
R"(10@symbol '[{"txid":"id","vout":0}]' address)")
+ HelpExampleRpc("minttokens", R"(10@symbol '[{"txid":"id","vout":0}]')")
},
}.Check(request);

Expand All @@ -676,8 +681,32 @@ UniValue minttokens(const JSONRPCRequest& request) {
}
pwallet->BlockUntilSyncedToCurrentChain();

const CBalances minted = DecodeAmounts(pwallet->chain(), request.params[0], "");
UniValue const & txInputs = request.params[1];
CBalances minted;
UniValue txInputs;
CScript to;

if (request.params[0].isObject()) {
auto optionsObj = request.params[0].get_obj();
minted = DecodeAmounts(pwallet->chain(), optionsObj["amounts"].get_array(), "");

if (optionsObj.exists("inputs"))
txInputs = optionsObj["inputs"].get_array();

if (optionsObj.exists("to"))
to = DecodeScript(optionsObj["to"].get_str());
}
else {
minted = DecodeAmounts(pwallet->chain(), request.params[0], "");
txInputs = request.params[1];

if (request.params.size() > 2)
to = DecodeScript(request.params[2].get_str());
}

CMintTokensMessage mintTokensMessage;
mintTokensMessage.balances = minted.balances;
if (!to.empty())
mintTokensMessage.to = to;

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

Expand Down Expand Up @@ -731,8 +760,7 @@ UniValue minttokens(const JSONRPCRequest& request) {
rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, needFoundersAuth, optAuthTx, txInputs);

CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION);
metadata << static_cast<unsigned char>(CustomTxType::MintToken)
<< minted; /// @note here, that whole CBalances serialized!, not a 'minted.balances'!
metadata << static_cast<unsigned char>(CustomTxType::MintToken) << mintTokensMessage;

CScript scriptMeta;
scriptMeta << OP_RETURN << ToByteVector(metadata);
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
BuriedForkDescPushBack(softforks, "fortcanninggreatworld", consensusParams.FortCanningGreatWorldHeight);
BuriedForkDescPushBack(softforks, "fortcanningepilogue", consensusParams.FortCanningEpilogueHeight);
BuriedForkDescPushBack(softforks, "grandcentral", consensusParams.GrandCentralHeight);
BuriedForkDescPushBack(softforks, "grandcentralnext", consensusParams.GrandCentralNextHeight);
BuriedForkDescPushBack(softforks, "grandcentralepilogue", consensusParams.GrandCentralEpilogueHeight);
BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
obj.pushKV("softforks", softforks);

Expand Down
31 changes: 27 additions & 4 deletions test/functional/feature_consortium.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
self.extra_args = [
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1']]
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-grandcentralepilogueheight=300', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-grandcentralepilogueheight=300', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-grandcentralepilogueheight=300', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-fortcanninghillheight=50', '-fortcanningparkheight=50', '-fortcanningroadheight=50', '-fortcanningcrunchheight=50', '-fortcanningspringheight=50', '-fortcanninggreatworldheight=250', '-grandcentralheight=254', '-grandcentralepilogueheight=300', '-regtest-minttoken-simulate-mainnet=1', '-txindex=1']]

def run_test(self):

Expand Down Expand Up @@ -525,5 +525,28 @@ def run_test(self):
assert_raises_rpc_error(-5, "Amount must be positive or -1", self.nodes[0].setgov, {
"ATTRIBUTES": {'v0/consortium/' + idBTC + '/mint_limit': '-2'}})

# Test setting before fork
assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set before GrandCentralEpilogueHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/feature/mint-tokens-to-address':'true'}})

# Move to fork
self.nodes[0].generate(300 - self.nodes[0].getblockcount())

# Try and mint to an address before feature enabled
newAddress = self.nodes[0].getnewaddress("", "bech32")
assert_raises_rpc_error(-32600, "Mint tokens to address is not enabled", self.nodes[0].minttokens, {"amounts": ["2@" + symbolBTC], "to": newAddress})

# Enable mint tokens to an address
self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/feature/mint-tokens-to-address':'true'}})
self.nodes[0].generate(1)

# Mint tokens to an address
self.nodes[0].minttokens({"amounts": ["2@" + symbolBTC], "to": newAddress})
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getaccount(newAddress), ['2.00000000@BTC'])

assert_raises_rpc_error(-5, "recipient (NOTANADDRESS) does not refer to any valid address",
self.nodes[0].minttokens, {"amounts": ["2@" + symbolBTC], "to": "NOTANADDRESS"})


if __name__ == '__main__':
ConsortiumTest().main()
2 changes: 1 addition & 1 deletion test/functional/rpc_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _test_getblockchaininfo(self):
'fortcanninggreatworld': {'type': 'buried', 'active': False, 'height': 10000000},
'fortcanningepilogue': {'type': 'buried', 'active': False, 'height': 10000000},
'grandcentral': {'type': 'buried', 'active': False, 'height': 10000000},
'grandcentralnext': {'type': 'buried', 'active': False, 'height': 10000000},
'grandcentralepilogue': {'type': 'buried', 'active': False, 'height': 10000000},
'testdummy': {
'type': 'bip9',
'bip9': {
Expand Down

0 comments on commit d60f1bf

Please sign in to comment.