Skip to content

Commit

Permalink
Merge pull request bitcoin#27 from qtumproject/neil/QTUMCORE-7
Browse files Browse the repository at this point in the history
QTUMCORE-7: Implement createcontract
  • Loading branch information
Earlz committed Apr 6, 2017
2 parents 51411b2 + 4971788 commit 3901185
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/primitives/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class CTxOut

bool IsDust(const CFeeRate &minRelayTxFee) const
{
return (nValue < GetDustThreshold(minRelayTxFee));
return (nValue < GetDustThreshold(minRelayTxFee) && !this->scriptPubKey.HasOpCreate() && !this->scriptPubKey.HasOpCall());
}

friend bool operator==(const CTxOut& a, const CTxOut& b)
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getmempoolancestors", 1, "verbose" },
{ "getmempooldescendants", 1, "verbose" },
{ "bumpfee", 1, "options" },
{ "createcontract", 1, "gasLimit" },
{ "createcontract", 2, "gasPrice" },
{ "createcontract", 4, "broadcast" },
// Echo with conversion (For testing only)
{ "echojson", 0, "arg0" },
{ "echojson", 1, "arg1" },
Expand Down
2 changes: 1 addition & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
dFreeCount += nSize;
}

if (nAbsurdFee && nFees > nAbsurdFee)
if (!tx.HasCreateOrCall() && nAbsurdFee && nFees > nAbsurdFee)
return state.Invalid(false,
REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee));
Expand Down
3 changes: 3 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ static const int MAX_UNCONNECTING_HEADERS = 10;

static const bool DEFAULT_PEERBLOOMFILTERS = true;

static const uint64_t DEFAULT_GAS_LIMIT=10000;
static const CAmount DEFAULT_GAS_PRICE=0.00001*COIN;

struct BlockHasher
{
size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); }
Expand Down
174 changes: 173 additions & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,178 @@ UniValue sendtoaddress(const JSONRPCRequest& request)

return wtx.GetHash().GetHex();
}
UniValue createcontract(const JSONRPCRequest& request){

if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
if (request.fHelp || request.params.size() < 1 || request.params.size() > 5)
throw runtime_error(
"createcontract \"bytecode\" (gaslimit gasprice \"senderaddress\" broadcast)"
"\nCreate a contract with bytcode.\n"
+ HelpRequiringPassphrase() +
"\nArguments:\n"
"1. \"bytecode\" (string, required) contract bytcode.\n"
"2. gasLimit (numeric or string, optional) gasLimit, default: "+i64tostr(DEFAULT_GAS_LIMIT)+"\n"
"3. gasPrice (numeric or string, optional) gasPrice QTUM price per gas unit, default: "+FormatMoney(DEFAULT_GAS_PRICE)+"\n"
"4. \"senderaddress\" (string, optional) The quantum address that will be used to create the contract.\n"
"5. \"broadcast\" (bool, optional, default=true) Whether to broadcast the transaction or not.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"txid\" : (string) The transaction id.\n"
" \"sender\" : (string) " + CURRENCY_UNIT + " address of the sender.\n"
" \"hash160\" : (string) ripemd-160 hash of the sender.\n"
" \"address\" : (string) expected contract address.\n"
" }\n"
"]\n"
"\nExamples:\n"
+ HelpExampleCli("createcontract", "\"60606040525b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690836c010000000000000000000000009081020402179055506103786001600050819055505b600c80605b6000396000f360606040526008565b600256\"")
+ HelpExampleCli("createcontract", "\"60606040525b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690836c010000000000000000000000009081020402179055506103786001600050819055505b600c80605b6000396000f360606040526008565b600256\" 6000000 0.00000001 \"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" true")
);

LOCK2(cs_main, pwalletMain->cs_wallet);

string bytecode=request.params[0].get_str();

uint64_t nGasLimit=DEFAULT_GAS_LIMIT;
if (request.params.size() > 1){
nGasLimit = request.params[1].get_int64();
if (nGasLimit <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit");
}

CAmount nGasPrice = DEFAULT_GAS_PRICE;
if (request.params.size() > 2){
nGasPrice = request.params[2].get_real()*COIN;
if (nGasPrice <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice");
}

bool fHasSender=false;
CBitcoinAddress senderAddress;
if (request.params.size() > 3){
senderAddress.SetString(request.params[3].get_str());
if (!senderAddress.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address to send from");
else
fHasSender=true;
}

bool fBroadcast=true;
if (request.params.size() > 4){
fBroadcast=request.params[4].get_bool();
}

CCoinControl coinControl;

if(fHasSender){
//find a UTXO with sender address

UniValue results(UniValue::VARR);
vector<COutput> vecOutputs;

coinControl.fAllowOtherInputs=true;

assert(pwalletMain != NULL);
pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);

BOOST_FOREACH(const COutput& out, vecOutputs) {
CTxDestination address;
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);

CBitcoinAddress destAdress(address);

if (!fValidAddress || senderAddress.Get() != destAdress.Get())
continue;

coinControl.Select(COutPoint(out.tx->GetHash(),out.i));

break;

}

if(!coinControl.HasSelected()){
throw JSONRPCError(RPC_TYPE_ERROR, "Sender address does not have any unspent outputs");
}
}
EnsureWalletIsUnlocked();

CWalletTx wtx;

wtx.nTimeSmart = GetAdjustedTime();

CAmount nGasFee=nGasPrice*nGasLimit;

CAmount curBalance = pwalletMain->GetBalance();

// Check amount
if (nGasFee <= 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");

if (nGasFee > curBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");

// Build OP_EXEC script
CScript scriptPubKey = CScript() << ParseHex("01") << CScriptNum(nGasLimit) << CScriptNum(nGasPrice) << ParseHex(bytecode) <<OP_CREATE;

// Create and send the transaction
CReserveKey reservekey(pwalletMain);
CAmount nFeeRequired;
std::string strError;
vector<CRecipient> vecSend;
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, 0, false};
vecSend.push_back(recipient);

if (!pwalletMain->CreateTransaction(vecSend, wtx, reservekey, nFeeRequired, nChangePosRet, strError, &coinControl, true, nGasFee, fHasSender)) {
if (nFeeRequired > pwalletMain->GetBalance())
strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}

CTxDestination txSenderDest;
ExtractDestination(pwalletMain->mapWallet[wtx.tx->vin[0].prevout.hash].tx->vout[wtx.tx->vin[0].prevout.n].scriptPubKey,txSenderDest);

if (fHasSender && !(senderAddress.Get() == txSenderDest)){
throw JSONRPCError(RPC_TYPE_ERROR, "Sender could not be set, transaction was not committed!");
}

UniValue result(UniValue::VOBJ);
if(fBroadcast){
CValidationState state;
if (!pwalletMain->CommitTransaction(wtx, reservekey, g_connman.get(), state))
throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");

std::string txId=wtx.GetHash().GetHex();
result.push_back(Pair("txid", txId));

CBitcoinAddress txSenderAdress(txSenderDest);
CKeyID keyid;
txSenderAdress.GetKeyID(keyid);

result.push_back(Pair("sender", txSenderAdress.ToString()));
result.push_back(Pair("hash160", HexStr(valtype(keyid.begin(),keyid.end()))));

std::vector<unsigned char> SHA256TxVout(32);
vector<unsigned char> contractAddress(20);
vector<unsigned char> txIdAndVout(wtx.GetHash().begin(), wtx.GetHash().end());
unsigned char nOut=0;
BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout) {
if(txout.scriptPubKey.HasOpCreate()){
txIdAndVout.push_back(nOut);
}
nOut++;
}
CSHA256().Write(txIdAndVout.data(), txIdAndVout.size()).Finalize(SHA256TxVout.data());
CRIPEMD160().Write(SHA256TxVout.data(), SHA256TxVout.size()).Finalize(contractAddress.data());
result.push_back(Pair("address", HexStr(contractAddress)));
}else{
string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags());
result.push_back(Pair("raw transaction", strHex));
}
return result;
}
UniValue listaddressgroupings(const JSONRPCRequest& request)
{
if (!EnsureWalletIsAvailable(request.fHelp))
Expand Down Expand Up @@ -1793,7 +1964,7 @@ UniValue gettransaction(const JSONRPCRequest& request)
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
" \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
" 'send' category of transactions.\n"
" 'send' category of transactions.\n"
" }\n"
" ,...\n"
" ],\n"
Expand Down Expand Up @@ -3045,6 +3216,7 @@ static const CRPCCommand commands[] =
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} },
{ "wallet", "removeprunedfunds", &removeprunedfunds, true, {"txid"} },
{ "wallet", "createcontract", &createcontract, false, {"bytecode", "gasLimit", "gasPrice", "senderAddress", "broadcast"} },
};

void RegisterWalletRPCCommands(CRPCTable &t)
Expand Down
28 changes: 23 additions & 5 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "base58.h"
#include "checkpoints.h"
#include "chain.h"
#include "wallet/coincontrol.h"
//#include "wallet/coincontrol.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "key.h"
Expand Down Expand Up @@ -2370,11 +2370,17 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
}

bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, CAmount nGasFee, bool hasSender)
{
CAmount nValue = 0;
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
COutPoint senderInput;
if(hasSender && coinControl && coinControl->HasSelected()){
std::vector<COutPoint> vSenderInputs;
coinControl->ListSelected(vSenderInputs);
senderInput=vSenderInputs[0];
}
for (const auto& recipient : vecSend)
{
if (nValue < 0 || recipient.nAmount < 0)
Expand Down Expand Up @@ -2591,6 +2597,18 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
else
reservekey.ReturnKey();

// Move sender input to position 0
vector<pair<const CWalletTx*,unsigned int>> vCoins(setCoins.begin(),setCoins.end());
if(hasSender && coinControl && coinControl->HasSelected()){
for (std::vector<pair<const CWalletTx*,unsigned int>>::size_type i = 0 ; i != vCoins.size(); i++){
if(COutPoint(vCoins[i].first->GetHash(),vCoins[i].second)==senderInput){
if(i==0)break;
iter_swap(vCoins.begin(),vCoins.begin()+i);
break;
}
}
}

// Fill vin
//
// Note how the sequence number is set to non-maxint so that
Expand All @@ -2601,12 +2619,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// to avoid conflicting with other possible uses of nSequence,
// and in the spirit of "smallest possible change from prior
// behavior."
for (const auto& coin : setCoins)
for (const auto& coin : vCoins)
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
std::numeric_limits<unsigned int>::max() - (fWalletRbf ? 2 : 1)));

// Fill in dummy signatures for fee calculation.
if (!DummySignTx(txNew, setCoins)) {
if (!DummySignTx(txNew, vCoins)) {
strFailReason = _("Signing transaction failed");
return false;
}
Expand Down Expand Up @@ -2637,7 +2655,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
break;
}

CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool)+nGasFee;
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
Expand Down
6 changes: 3 additions & 3 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "wallet/crypter.h"
#include "wallet/walletdb.h"
#include "wallet/rpcwallet.h"

#include "wallet/coincontrol.h"
#include <algorithm>
#include <atomic>
#include <map>
Expand Down Expand Up @@ -75,7 +75,7 @@ static const bool DEFAULT_USE_HD_WALLET = true;
extern const char * DEFAULT_WALLET_DAT;

class CBlockIndex;
class CCoinControl;
//class CCoinControl;
class COutput;
class CReserveKey;
class CScript;
Expand Down Expand Up @@ -811,7 +811,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, CAmount nGasFee=0, bool hasSender=false);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state);

void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
Expand Down

0 comments on commit 3901185

Please sign in to comment.