diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 908d63eb2..1715fb3d4 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -443,6 +443,7 @@ target_sources (rippled PRIVATE src/ripple/app/tx/impl/SetRegularKey.cpp src/ripple/app/tx/impl/SetHook.cpp src/ripple/app/tx/impl/ClaimReward.cpp + src/ripple/app/tx/impl/GenesisMint.cpp src/ripple/app/tx/impl/Import.cpp src/ripple/app/tx/impl/Invoke.cpp src/ripple/app/tx/impl/SetSignerList.cpp diff --git a/hook/makefile b/hook/makefile index 40027cf23..ea781b614 100644 --- a/hook/makefile +++ b/hook/makefile @@ -1,4 +1,4 @@ -all: reward govern +all: reward govern mint reward: wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../ wasm-opt reward.wasm -o reward.wasm \ @@ -109,3 +109,58 @@ govern: -Oz hook-cleaner govern.wasm guard_checker govern.wasm +mint: + wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../ + wasm-opt mint.wasm -o mint.wasm \ + --shrink-level=100000000 \ + --coalesce-locals-learning \ + --vacuum \ + --merge-blocks \ + --merge-locals \ + --flatten \ + --ignore-implicit-traps \ + -ffm \ + --const-hoisting \ + --code-folding \ + --code-pushing \ + --dae-optimizing \ + --dce \ + --simplify-globals-optimizing \ + --simplify-locals-nonesting \ + --reorder-locals \ + --rereloop \ + --precompute-propagate \ + --local-cse \ + --remove-unused-brs \ + --memory-packing \ + -c \ + --avoid-reinterprets \ + -Oz + hook-cleaner mint.wasm + wasm-opt mint.wasm -o mint.wasm \ + --shrink-level=100000000 \ + --coalesce-locals-learning \ + --vacuum \ + --merge-blocks \ + --merge-locals \ + --flatten \ + --ignore-implicit-traps \ + -ffm \ + --const-hoisting \ + --code-folding \ + --code-pushing \ + --dae-optimizing \ + --dce \ + --simplify-globals-optimizing \ + --simplify-locals-nonesting \ + --reorder-locals \ + --rereloop \ + --precompute-propagate \ + --local-cse \ + --remove-unused-brs \ + --memory-packing \ + -c \ + --avoid-reinterprets \ + -Oz + hook-cleaner mint.wasm + guard_checker mint.wasm diff --git a/hook/mint.c b/hook/mint.c new file mode 100644 index 000000000..27bb97eb8 --- /dev/null +++ b/hook/mint.c @@ -0,0 +1,114 @@ +// This hook just tests GenesisMint transactor, it is not for production use + +#include "hookapi.h" +#define ASSERT(x)\ + if (!(x))\ + rollback(SBUF("Reward: Assertion failure."),__LINE__); + +#define DEBUG 1 + +uint8_t txn_mint[13844] = +{ +/* size,upto */ +/* 3, 0 */ 0x12U, 0x00U, 0x60U, /* tt = GenesisMint */ +/* 5, 3 */ 0x22U, 0x80U, 0x00U, 0x00U, 0x00U, /* flags = tfCanonical */ +/* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */ +/* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */ +/* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */ +/* 9, 25 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ +/* 35, 34 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */ +/* 22, 69 */ 0x81U, 0x14U, 0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U, + 0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U, /* src acc */ + +/* 116, 91 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* emit detail */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + +/* 207, ... */ 0xF0U, 0x60U, /* gen mints arr */ +/* 34 bytes per entries + 1 tail byte + E060 + 61 + 4111111111111111 // amount + 8114 + 1234567891234567891234567891234567891234 // account + E1 + ... repeat + F1 // tail byte + * + * */ +}; + +#define BE_DROPS(drops)\ +{\ + uint64_t drops_tmp = drops;\ + uint8_t* b = (uint8_t*)&drops;\ + *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ + *b++ = (drops_tmp >> 48) & 0xFFU;\ + *b++ = (drops_tmp >> 40) & 0xFFU;\ + *b++ = (drops_tmp >> 32) & 0xFFU;\ + *b++ = (drops_tmp >> 24) & 0xFFU;\ + *b++ = (drops_tmp >> 16) & 0xFFU;\ + *b++ = (drops_tmp >> 8) & 0xFFU;\ + *b++ = (drops_tmp >> 0) & 0xFFU;\ +} + +int64_t hook(uint32_t r) +{ + etxn_reserve(1); + _g(1,1); + + + // emit the txn + uint64_t drops = 200; + BE_DROPS(drops); + + + uint8_t* upto = txn_mint + 209U; + + *upto++ = 0xE0U; // obj start + *upto++ = 0x60U; + + *upto++ = 0x61U; // amt + *((uint64_t*)upto) = drops; + upto += 8; + + *upto++ = 0x83U; // acc + *upto++ = 0x14U; + otxn_field(upto, 20, sfDestination); + upto += 20; + *upto++ = 0xE1U; // obj end + *upto++ = 0xF1U; // array end + + etxn_details(txn_mint + 91, 116); + int64_t fee = etxn_fee_base(txn_mint, upto - txn_mint); + BE_DROPS(fee); + + *((uint64_t*)(txn_mint + 26)) = fee; + + int64_t seq = ledger_seq() + 1; + txn_mint[15] = (seq >> 24U) & 0xFFU; + txn_mint[16] = (seq >> 16U) & 0xFFU; + txn_mint[17] = (seq >> 8U) & 0xFFU; + txn_mint[18] = seq & 0xFFU; + + seq += 4; + txn_mint[21] = (seq >> 24U) & 0xFFU; + txn_mint[22] = (seq >> 16U) & 0xFFU; + txn_mint[23] = (seq >> 8U) & 0xFFU; + txn_mint[24] = seq & 0xFFU; + + + trace(SBUF("emit:"), txn_mint, upto-txn_mint, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_mint, upto - txn_mint); + + if (DEBUG) + TRACEVAR(emit_result); + + if (emit_result < 0) + rollback(SBUF("MintTest: Emit failed."), __LINE__); + + + accept(SBUF("MintTest: Emitted txn successfully."), __LINE__); +} diff --git a/src/ripple/app/tx/impl/GenesisMint.cpp b/src/ripple/app/tx/impl/GenesisMint.cpp new file mode 100644 index 000000000..e8b13d220 --- /dev/null +++ b/src/ripple/app/tx/impl/GenesisMint.cpp @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { + +TxConsequences +GenesisMint::makeTxConsequences(PreflightContext const& ctx) +{ + // RH TODO: review this + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +NotTEC +GenesisMint::preflight(PreflightContext const& ctx) +{ + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto& tx = ctx.tx; + + auto const id = ctx.tx[sfAccount]; + + static auto const genesisAccountId = calcAccountID( + generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase")) + .first); + + if (id != genesisAccountId || !tx.isFieldPresent(sfEmitDetails)) + { + JLOG(ctx.j.warn()) + << "GenesisMint: can only be used by the genesis account in an emitted transaction."; + return temMALFORMED; + } + + auto const& dests = tx.getFieldArray(sfGenesisMints); + if (dests.empty()) + { + JLOG(ctx.j.warn()) + << "GenesisMint: destinations array empty."; + return temMALFORMED; + } + + if (dests.size() > 512) + { + JLOG(ctx.j.warn()) + << "GenesisMint: destinations array exceeds 512 entries."; + return temMALFORMED; + } + + std::unordered_set alreadySeen; + for (auto const& dest: dests) + { + if (dest.getFName() != sfGenesisMint) + { + JLOG(ctx.j.warn()) + << "GenesisMint: destinations array contained an invalid entry."; + return temMALFORMED; + } + + auto const amt = dest.getFieldAmount(sfAmount); + if (!isXRP(amt)) + { + JLOG(ctx.j.warn()) + << "GenesisMint: only native amounts can be minted."; + return temMALFORMED; + } + + if (amt <= beast::zero) + { + JLOG(ctx.j.warn()) + << "GenesisMint: only positive amounts can be minted."; + return temMALFORMED; + } + + auto const accid = dest.getAccountID(sfDestination); + + if (accid == noAccount() || accid == xrpAccount()) + { + JLOG(ctx.j.warn()) + << "GenesisMint: destinations includes disallowed account zero or one."; + return temMALFORMED; + } + + if (alreadySeen.find(accid) != alreadySeen.end()) + { + JLOG(ctx.j.warn()) + << "GenesisMint: duplicate in destinations."; + return temMALFORMED; + } + + alreadySeen.emplace(accid); + } + + return preflight2(ctx); +} + +TER +GenesisMint::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureHooks)) + return temDISABLED; + + if (!ctx.view.rules().enabled(featureXahauGenesis)) + return temDISABLED; + + // RH UPTO: + // check that printing won't exceed 200% of the total coins on the ledger + // this will act as a hard cap against malfunction + // modify the invariant checkers + + return tesSUCCESS; +} + +TER +GenesisMint::doApply() +{ + auto const& dests = ctx_.tx.getFieldArray(sfGenesisMints); + + for (auto const& dest: dests) + { + auto const amt = dest.getFieldAmount(sfAmount); + auto const id = dest.getAccountID(sfDestination); + auto const k = keylet::account(id); + auto sle = view().peek(k); + if (!sle) + { + // Create the account. + std::uint32_t const seqno{ + view().rules().enabled(featureDeletableAccounts) ? view().seq() + : 1}; + sle = std::make_shared(k); + sle->setAccountID(sfAccount, id); + + sle->setFieldU32(sfSequence, seqno); + sle->setFieldAmount(sfBalance, amt); + + view().insert(sle); + } + else + { + // Credit the account + STAmount startBal = sle->getFieldAmount(sfBalance); + STAmount finalBal = startBal + amt; + if (finalBal > startBal) + { + sle->setFieldAmount(sfBalance, finalBal); + view().update(sle); + } + else + { + JLOG(ctx_.journal.warn()) + << "GenesisMint: cannot credit " << dest << " due to balance overflow"; + } + } + } + + return tesSUCCESS; +} + +XRPAmount +GenesisMint::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + return Transactor::calculateBaseFee(view, tx); +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/GenesisMint.h b/src/ripple/app/tx/impl/GenesisMint.h new file mode 100644 index 000000000..83a716586 --- /dev/null +++ b/src/ripple/app/tx/impl/GenesisMint.h @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_GENESISMINT_H_INCLUDED +#define RIPPLE_TX_GENESISMINT_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class GenesisMint : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + explicit GenesisMint(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index ac1b89973..169cf0bfd 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace ripple { @@ -156,6 +157,8 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttCLAIM_REWARD: return invoke_preflight_helper(ctx); + case ttGENESIS_MINT: + return invoke_preflight_helper(ctx); case ttIMPORT: return invoke_preflight_helper(ctx); case ttINVOKE: @@ -275,6 +278,8 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttCLAIM_REWARD: return invoke_preclaim(ctx); + case ttGENESIS_MINT: + return invoke_preclaim(ctx); case ttIMPORT: return invoke_preclaim(ctx); case ttINVOKE: @@ -353,6 +358,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) return NFTokenAcceptOffer::calculateBaseFee(view, tx); case ttCLAIM_REWARD: return ClaimReward::calculateBaseFee(view, tx); + case ttGENESIS_MINT: + return GenesisMint::calculateBaseFee(view, tx); case ttIMPORT: return Import::calculateBaseFee(view, tx); case ttINVOKE: @@ -525,6 +532,10 @@ invoke_apply(ApplyContext& ctx) ClaimReward p(ctx); return p(); } + case ttGENESIS_MINT: { + GenesisMint p(ctx); + return p(); + } case ttIMPORT: { Import p(ctx); return p(); diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 22fd2957f..33c6a0546 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -336,7 +336,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT, CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22); CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23); CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24); -CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 25); +CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96); // array of objects // ARRAY/1 is reserved for end of array @@ -357,7 +357,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18); CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19); CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20); -CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 21); +CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96); // clang-format on