mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-01 16:05:52 +00:00
Housekeeping (#170)
* Update pull_request_template.md * strip out unused files * Update CONTRIBUTING.md * fix docker * remove conan stuff * update license * update hook directory * Delete .codecov.yml * Update .dockerignore * Update genesis.json * update validator list example * Update rippled-standalone.cfg * Update CONTRIBUTING.md
This commit is contained in:
703
hook/genesis/govern.c
Normal file
703
hook/genesis/govern.c
Normal file
@@ -0,0 +1,703 @@
|
||||
#include "hookapi.h"
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback(SBUF("Govern: Assertion failed."),__LINE__);
|
||||
|
||||
#define SEAT_COUNT 20
|
||||
|
||||
#define HOOK_MAX 10 // maximum number of hooks on an account
|
||||
|
||||
/**
|
||||
* Xahau Governance Hook
|
||||
*
|
||||
* The governance model is a 20 seat round table.
|
||||
* Each seat may be filled or empty (provided there is at least one filled seat.)
|
||||
* The primary governance table sits on the genesis account. This is also called the L1 table.
|
||||
* Seats at this table are called L1s or L1 members.
|
||||
* At L1, for votes relating to table membership 80% of the filled seats must vote in favour.
|
||||
* At L1, for votes relating to everything else 100% of the filled seats must vote in favour.
|
||||
* One or more L1 seats may contain an account that has an L2 governance hook installed on it and is blackholed.
|
||||
* This is referred to as an L2 table. The seats at the table are called L2s or L2 members.
|
||||
* There may be multiple L2 tables.
|
||||
*
|
||||
* Hook Parameters:
|
||||
*
|
||||
* // both table types uses these parameters V
|
||||
*
|
||||
* Parameter Name: {'I', 'M', 'C'}
|
||||
* Parameter Value: Initial Member Count <1 byte>
|
||||
*
|
||||
* Parameter Name: {'I', 'S', '\0'}
|
||||
* Parameter Value: Initial seat #0's member's 20 byte Account ID.
|
||||
*
|
||||
* // only L1 table uses these parameters V
|
||||
*
|
||||
* Parameter Name: {'I', 'R', 'R'}
|
||||
* Parameter Value: Initial Reward Rate <8 byte XFL fraction between 0 and 1, LE>
|
||||
*
|
||||
* Parameter Name: {'I', 'R', 'D'}
|
||||
* Parameter Value: Initial Reward Delay <8 byte LE XFL seconds between rewards>
|
||||
* ...
|
||||
*
|
||||
* Topics:
|
||||
* 'H[0-9]' - Hook Hash in positions 0-9 <32 byte hash> on genesis
|
||||
* 'RR' - reward rate <le xfl 8 bytes>
|
||||
* 'RD' - reward delay <le xfl 8 bytes>
|
||||
* 'S[0-19]' - who is a governance member occupying that seat <20 byte accid>
|
||||
*
|
||||
* Hook State:
|
||||
* State Key: {0..0, 'M', 'C'}
|
||||
* State Data: Current member count <1 byte>
|
||||
*
|
||||
* State Key: {0..0, 'R', 'R'}
|
||||
* State Data: Current reward rate <8 byte LE XFL> (L1 table only)
|
||||
*
|
||||
* State Key: {0..0, 'R', 'D'}
|
||||
* State Data: Current reward delay <8 byte LE XFL> (L1 table only)
|
||||
*
|
||||
* State Key: {0..0, '\0 + seat id'}
|
||||
* State Data: 20 byte account ID for the member who occupies this seat. If absent unoccupied.
|
||||
*
|
||||
* State Key: {0..0, <20 byte account id>}
|
||||
* State Data: Seat number this member occupies <1 byte>
|
||||
*
|
||||
* State Key: {'V', 'H|R|S' <topic type>, '\0 + topic id', <layer>, 0..0, <member accid>}
|
||||
* State Data: A vote by a member for a topic and topic data
|
||||
*
|
||||
* State Key: {'C', 'H|R|S' <topic type>, '\0 + topic id', <layer>, 0*, <front truncated topic data>}
|
||||
* State Data: The number of members who have voted for that topic data and topic combination <1 byte>
|
||||
*
|
||||
* Hook Invocation:
|
||||
* ttINVOKE:
|
||||
* First time:
|
||||
* Behaviour: Setup hook, setup initial accounts, end (accept).
|
||||
*
|
||||
* Subsequent:
|
||||
* Behaviour: Vote on a topic, if the votes meet the topic vote threshold, action the topic.
|
||||
*
|
||||
* Parameter Name: {'L'}
|
||||
* Parameter Value: Which layer the vote is inteded for (ONLY L2 TABLES USE THIS PARAMETER)
|
||||
* { 1 a vote cast by an L2 member about an L1 topic }, or
|
||||
* { 2 a vote cast by an L2 member about an L2 topic }
|
||||
*
|
||||
*
|
||||
* Parameter Name: {'T'}
|
||||
* Parameter Value: The topic to vote on <2 bytes>
|
||||
* { 'S|H' (seat, hook), '\0 + topic id' }, or
|
||||
* { 'R' (reward), 'R|D' (rate, delay) }
|
||||
*
|
||||
* Parameter Name: {'V'}
|
||||
* Parameter Value: The data to vote for this topic (accid, hook hash, reward rate/delay)
|
||||
**/
|
||||
|
||||
#define SVAR(x) &x, sizeof(x)
|
||||
|
||||
#define DONE(x)\
|
||||
accept(SBUF(x),__LINE__);
|
||||
|
||||
#define NOPE(x)\
|
||||
rollback(SBUF(x), __LINE__);
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
// genesis account id
|
||||
uint8_t genesis[20] =
|
||||
{0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U,
|
||||
0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U};
|
||||
|
||||
uint8_t zero32[32];
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
etxn_reserve(1);
|
||||
|
||||
// in debug mode the test case can supply a line number that the hook will then print here.
|
||||
{
|
||||
uint8_t ln[2];
|
||||
if (otxn_param(SBUF(ln), "D", 1) == 2)
|
||||
{
|
||||
uint16_t lineno = (((uint16_t)ln[0]) << 8U) + ln[1];
|
||||
trace_num(SBUF("DBGLN"), lineno);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t tt = otxn_type();
|
||||
|
||||
if (tt != 99) // ttINVOKE only
|
||||
DONE("Governance: Passing non-Invoke txn. HookOn should be changed to avoid this.");
|
||||
|
||||
// get the account id
|
||||
uint8_t account_field[32];
|
||||
otxn_field(account_field + 12, 20, sfAccount);
|
||||
|
||||
uint8_t hook_accid[32];
|
||||
hook_account(hook_accid + 12, 20);
|
||||
|
||||
// outgoing txns to other hooks allowed
|
||||
if (BUFFER_EQUAL_20(hook_accid + 12, account_field + 12))
|
||||
{
|
||||
uint8_t dest_acc[20];
|
||||
if (otxn_field(SBUF(dest_acc), sfDestination) == 20 && !BUFFER_EQUAL_20(hook_accid + 12, dest_acc))
|
||||
DONE("Goverance: Passing outgoing txn.");
|
||||
}
|
||||
|
||||
int64_t is_L1_table = BUFFER_EQUAL_20(hook_accid + 12, genesis);
|
||||
|
||||
if (is_L1_table)
|
||||
trace(SBUF("Governance: Starting governance logic on L1 table."), 0,0,0);
|
||||
else
|
||||
trace(SBUF("Governance: Starting governance logic on L2 table."), 0,0,0);
|
||||
|
||||
int64_t member_count = state(0,0, "MC", 2);
|
||||
|
||||
// initial execution, setup hook
|
||||
if (member_count == DOESNT_EXIST)
|
||||
{
|
||||
// gather hook parameters
|
||||
|
||||
uint8_t imc;
|
||||
uint64_t irr, ird;
|
||||
if (hook_param(SVAR(imc), "IMC", 3) < 0)
|
||||
NOPE("Governance: Initial Member Count Parameter missing (IMC).");
|
||||
TRACEVAR(imc);
|
||||
|
||||
// set member count
|
||||
ASSERT(0 < state_set(SVAR(imc), "MC", 2));
|
||||
|
||||
member_count = imc;
|
||||
TRACEVAR(member_count);
|
||||
|
||||
|
||||
if (imc == 0)
|
||||
NOPE("Governance: Initial Member Count must be > 0.");
|
||||
|
||||
if (imc > SEAT_COUNT)
|
||||
NOPE("Governance: Initial Member Count must be <= Seat Count (20).");
|
||||
|
||||
if (is_L1_table)
|
||||
{
|
||||
if (hook_param(SVAR(irr), "IRR", 3) < 0)
|
||||
NOPE("Governance: Initial Reward Rate Parameter missing (IRR).");
|
||||
|
||||
if (hook_param(SVAR(ird), "IRD", 3) < 0)
|
||||
NOPE("Governance: Initial Reward Delay Parameter miss (IRD).");
|
||||
|
||||
if (ird == 0)
|
||||
NOPE("Governance: Initial Reward Delay must be > 0.");
|
||||
|
||||
// set reward rate
|
||||
ASSERT(0 < state_set(SVAR(irr), "RR", 2));
|
||||
|
||||
// set reward delay
|
||||
ASSERT(0 < state_set(SVAR(ird), "RD", 2));
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; GUARD(SEAT_COUNT), i < member_count; ++i)
|
||||
{
|
||||
uint8_t member_acc[20];
|
||||
uint8_t member_pkey[3] = {'I', 'S', i};
|
||||
if (hook_param(SBUF(member_acc), member_pkey, 3) != 20)
|
||||
NOPE("Governance: One or more initial member account ID's is missing");
|
||||
|
||||
// 0... X where X is member id started from 1
|
||||
// maps to the member's account ID
|
||||
trace(SBUF("Member:"), SBUF(member_acc), 1);
|
||||
// reverse key
|
||||
ASSERT(state_set(SBUF(member_acc), SVAR(i)) == 20);
|
||||
|
||||
// 0, 0... ACCOUNT ID maps to member_id (as above)
|
||||
// forward key
|
||||
ASSERT(state_set(SVAR(i), SBUF(member_acc)) == 1);
|
||||
}
|
||||
|
||||
DONE("Governance: Setup completed successfully.");
|
||||
}
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(member_count);
|
||||
|
||||
|
||||
// otherwise a normal execution (not initial)
|
||||
// first let's check if the invoking party is a member
|
||||
|
||||
int64_t member_id = state(0,0,account_field + 12, 20);
|
||||
if (member_id < 0)
|
||||
NOPE("Governance: You are not currently a governance member at this table.");
|
||||
|
||||
// the only thing a member can do is vote for a topic
|
||||
// so lets process their vote
|
||||
|
||||
// { 'S|H|R', '\0 + topicid' }
|
||||
uint8_t topic[2];
|
||||
int64_t result = otxn_param(SBUF(topic), "T", 1);
|
||||
uint8_t t = topic[0]; // topic type
|
||||
uint8_t n = topic[1]; // number (seats) (or R/D for reward rate/delay)
|
||||
|
||||
if (result != 2 || (
|
||||
t != 'S' && // topic type: seat
|
||||
t != 'H' && // topic type: hook
|
||||
t != 'R')) // topic type: reward
|
||||
NOPE("Governance: Valid TOPIC must be specified as otxn parameter.");
|
||||
|
||||
|
||||
if (t == 'S' && n > (SEAT_COUNT - 1))
|
||||
NOPE("Governance: Valid seat topics are 0 through 19.");
|
||||
|
||||
if (t == 'H' && n > HOOK_MAX)
|
||||
NOPE("Governance: Valid hook topics are 0 through 9.");
|
||||
|
||||
if (t == 'R' && n != 'R' && n != 'D')
|
||||
NOPE("Governance: Valid reward topics are R (rate) and D (delay).");
|
||||
|
||||
// is their vote for the L2 table or the L1 table?
|
||||
uint8_t l = 1;
|
||||
if (!is_L1_table)
|
||||
{
|
||||
result = otxn_param(&l, 1, "L", 1);
|
||||
if (result != 1)
|
||||
NOPE("Governance: Missing L parameter. Which layer are you voting for?");
|
||||
|
||||
TRACEVAR(l);
|
||||
|
||||
if (l != 1 && l != 2)
|
||||
NOPE("Governance: Layer parameter must be '1' or '2'.");
|
||||
}
|
||||
|
||||
if (l == 2 && t == 'R')
|
||||
NOPE("Governance: L2s cannot vote on RR/RD at L2, did you mean to set L=1?");
|
||||
|
||||
|
||||
// RH TODO: validate RR/RD xfl > 0
|
||||
uint8_t topic_data[56 /* there's a 24 byte pad on the back for later logic */];
|
||||
uint8_t topic_size =
|
||||
t == 'H' ? 32 : // hook topics are a 32 byte hook hash
|
||||
t == 'S' ? 20 : // account topics are a 20 byte account ID
|
||||
8; // reward topics are an 8 byte le xfl
|
||||
|
||||
uint8_t padding = 32 - topic_size;
|
||||
|
||||
result = otxn_param(topic_data + padding, topic_size, "V", 1);
|
||||
if (result != topic_size)
|
||||
NOPE("Governance: Missing or incorrect size of VOTE data for TOPIC type.");
|
||||
|
||||
// set this flag if the topic data is all zeros
|
||||
int topic_data_zero =
|
||||
(*((uint64_t*)(topic_data + 0)) == 0) &&
|
||||
(*((uint64_t*)(topic_data + 8)) == 0) &&
|
||||
(*((uint64_t*)(topic_data + 16)) == 0) &&
|
||||
(*((uint64_t*)(topic_data + 24)) == 0);
|
||||
|
||||
trace(SBUF("topic_data_raw:"), topic_data, 56, 1);
|
||||
trace_num(SBUF("topic_padding:"), padding);
|
||||
trace_num(SBUF("topic_size:"), topic_size);
|
||||
trace(SBUF("topic_data:"), topic_data + padding, topic_size, 1);
|
||||
|
||||
// reuse account_field to create vote key
|
||||
account_field[0] = 'V';
|
||||
account_field[1] = t;
|
||||
account_field[2] = n;
|
||||
account_field[3] = l;
|
||||
|
||||
// get their previous vote if any on this topic
|
||||
uint8_t previous_topic_data[32];
|
||||
int64_t previous_topic_size =
|
||||
state(previous_topic_data + padding, topic_size, SBUF(account_field));
|
||||
|
||||
// check if the vote they're making has already been cast before,
|
||||
// if it is identical to their existing vote for this topic then just end with tesSUCCESS
|
||||
trace(SBUF("previous_topic_data"), previous_topic_data, 32, 1);
|
||||
trace(SBUF("topic_data"), topic_data, 32, 1);
|
||||
trace_num(SBUF("previous_topic_size"), previous_topic_size);
|
||||
trace_num(SBUF("topic_size"), topic_size);
|
||||
if (previous_topic_size == topic_size && BUFFER_EQUAL_32(previous_topic_data, topic_data))
|
||||
DONE("Governance: Your vote is already cast this way for this topic.");
|
||||
|
||||
// execution to here means the vote is different
|
||||
// we might have to decrement the old voting if they voted previously
|
||||
// and we will have to increment the new voting
|
||||
|
||||
// write vote to their voting key
|
||||
ASSERT(state_set(topic_data + padding, topic_size, SBUF(account_field)) == topic_size);
|
||||
|
||||
uint8_t previous_votes = 0;
|
||||
// decrement old vote counter for this option
|
||||
if (previous_topic_size > 0)
|
||||
{
|
||||
uint8_t votes = 0;
|
||||
// override the first two bytes to turn it into a vote count key
|
||||
previous_topic_data[0] = 'C';
|
||||
previous_topic_data[1] = t;
|
||||
previous_topic_data[2] = n;
|
||||
previous_topic_data[3] = l;
|
||||
|
||||
ASSERT(state(&votes, 1, SBUF(previous_topic_data)) == 1);
|
||||
ASSERT(votes > 0);
|
||||
previous_votes = votes;
|
||||
votes--;
|
||||
// delete the state entry if votes hit zero
|
||||
ASSERT(state_set(votes == 0 ? 0 : &votes, votes == 0 ? 0 : 1, SBUF(previous_topic_data)) >= 0);
|
||||
}
|
||||
|
||||
// increment new counter
|
||||
uint8_t votes = 0;
|
||||
{
|
||||
// we're going to clobber the topic data to turn it into a vote count key
|
||||
// so store the first bytes
|
||||
uint64_t saved_data = *((uint64_t*)topic_data);
|
||||
topic_data[0] = 'C';
|
||||
topic_data[1] = t;
|
||||
topic_data[2] = n;
|
||||
topic_data[3] = l;
|
||||
|
||||
state(&votes, 1, topic_data, 32);
|
||||
votes++;
|
||||
ASSERT(0 < state_set(&votes, 1, topic_data, 32));
|
||||
|
||||
// restore the saved bytes
|
||||
*((uint64_t*)topic_data) = saved_data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (DEBUG)
|
||||
{
|
||||
TRACEVAR(topic_data_zero);
|
||||
TRACEVAR(votes);
|
||||
TRACEVAR(member_count);
|
||||
trace(SBUF("topic"), topic, 2, 1);
|
||||
}
|
||||
|
||||
int64_t q80 = member_count * 0.8;
|
||||
int64_t q51 = member_count * 0.51;
|
||||
|
||||
if (q80 < 2)
|
||||
q80 = 2;
|
||||
|
||||
if (q51 < 2)
|
||||
q51 = 2;
|
||||
|
||||
if (is_L1_table || l == 2)
|
||||
{
|
||||
if (votes <
|
||||
(t == 'S'
|
||||
? q80 // L1s have 80% threshold for membership/seat voting
|
||||
: member_count)) // L1s have 100% threshold for all other voting
|
||||
DONE("Governance: Vote record. Not yet enough votes to action.");
|
||||
}
|
||||
else if (votes < q51)
|
||||
{
|
||||
// layer 2 table voting on a l1 topic
|
||||
DONE("Governance: Not yet enough votes to action L1 vote...");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// action vote
|
||||
if (DEBUG)
|
||||
TRACESTR("Actioning votes");
|
||||
|
||||
if (l == 1 && !is_L1_table)
|
||||
{
|
||||
|
||||
|
||||
uint8_t txn_out[1024];
|
||||
uint8_t* buf_out = txn_out;
|
||||
uint32_t cls = (uint32_t)ledger_seq();
|
||||
_01_02_ENCODE_TT (buf_out, ttINVOKE);
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL);
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 0);
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 1);
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 5);
|
||||
uint8_t* fee_ptr = buf_out;
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0);
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out);
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, hook_accid + 12);
|
||||
_08_03_ENCODE_ACCOUNT_DST (buf_out, genesis);
|
||||
int64_t edlen = etxn_details((uint32_t)buf_out, 512);
|
||||
buf_out += edlen;
|
||||
|
||||
/** Parameters:
|
||||
* F013E017 70180154
|
||||
* 701902
|
||||
* <00> <two byte topic code>
|
||||
* E1E0177018015670
|
||||
* 19
|
||||
* <topic len>
|
||||
* <topic data>
|
||||
* E1F1
|
||||
*/
|
||||
|
||||
// note wasm is LE and Txns are BE so these large constants are endian flipped
|
||||
// to undo the flipping on insertion into memory
|
||||
*((uint64_t*)buf_out) = 0x5401187017E013F0ULL; // parameters array, first parameter preamble
|
||||
buf_out += 8;
|
||||
*((uint32_t*)buf_out) = 0x021970UL;
|
||||
buf_out += 3;
|
||||
*buf_out++ = t; // topic
|
||||
*buf_out++ = n;
|
||||
*((uint64_t*)buf_out) = 0x705601187017E0E1ULL;
|
||||
buf_out += 8;
|
||||
*buf_out++ = 0x19U;
|
||||
// topic data len
|
||||
*buf_out++ = topic_size;
|
||||
uint64_t* d = (uint64_t*)buf_out;
|
||||
uint64_t* s = (uint64_t*)(topic_data + padding);
|
||||
*d++ = *s++;
|
||||
*d++ = *s++;
|
||||
*d++ = *s++;
|
||||
*d++ = *s++;
|
||||
buf_out += topic_size;
|
||||
// topicdata
|
||||
*((uint16_t*)buf_out) = 0xF1E1U;
|
||||
|
||||
int64_t txn_len = buf_out - txn_out;
|
||||
|
||||
// populate fee
|
||||
int64_t fee = etxn_fee_base(txn_out, txn_len);
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee );
|
||||
|
||||
trace(SBUF("Governance: Emitting invoke to L1"), txn_out, txn_len, 1);
|
||||
|
||||
|
||||
uint8_t emit_hash[32];
|
||||
int64_t emit_result = emit(SBUF(emit_hash), txn_out, txn_len);
|
||||
|
||||
trace_num(SBUF("Governance: Emit result"), emit_result);
|
||||
|
||||
if (emit_result == 32)
|
||||
DONE("Governance: Successfully emitted L1 vote.");
|
||||
|
||||
NOPE("Governance: L1 vote emission failed.");
|
||||
}
|
||||
|
||||
switch(t)
|
||||
{
|
||||
case 'R':
|
||||
{
|
||||
// reward topics
|
||||
int64_t result = state_set(topic_data + padding, topic_size, SBUF(topic));
|
||||
TRACEVAR(result);
|
||||
ASSERT(0 < result);
|
||||
if (n == 'R')
|
||||
DONE("Governance: Reward rate change actioned!");
|
||||
|
||||
DONE("Governance: Reward delay change actioned!");
|
||||
}
|
||||
|
||||
case 'H':
|
||||
{
|
||||
// hook topics
|
||||
|
||||
// first get the hook ledget object
|
||||
uint8_t keylet[34];
|
||||
util_keylet(SBUF(keylet), KEYLET_HOOK, hook_accid + 12, 20, 0,0,0,0);
|
||||
slot_set(SBUF(keylet), 5);
|
||||
|
||||
// now get the hooks array
|
||||
slot_subfield(5, sfHooks, 6);
|
||||
|
||||
// now check the entry
|
||||
if (slot_subarray(6, n, 7) == 7)
|
||||
{
|
||||
// it exists
|
||||
// check if its identical
|
||||
uint8_t existing_hook[32];
|
||||
if (slot_subfield(7, sfHookHash, 8) == 8)
|
||||
{
|
||||
ASSERT(slot(SBUF(existing_hook), 8) == 32);
|
||||
|
||||
// if it is then do nothing
|
||||
if (BUFFER_EQUAL_32(existing_hook, topic_data))
|
||||
DONE("Goverance: Target hook is already the same as actioned hook.");
|
||||
}
|
||||
}
|
||||
|
||||
// generate the hook definition keylet
|
||||
if (!topic_data_zero)
|
||||
{
|
||||
util_keylet(SBUF(keylet), KEYLET_HOOK_DEFINITION, topic_data, 32, 0,0,0,0);
|
||||
|
||||
// check if the ledger contains such a hook definition
|
||||
if (slot_set(SBUF(keylet), 9) != 9)
|
||||
NOPE("Goverance: Hook Hash doesn't exist on ledger while actioning hook.");
|
||||
}
|
||||
|
||||
// it does so now we can do the emit
|
||||
|
||||
uint8_t* hookhash =
|
||||
topic_data_zero
|
||||
? ((uint8_t*)0xFFFFFFFFU) // if the topic data is all zero then it's a delete operation
|
||||
: topic_data; // otherwise it's an install operation
|
||||
|
||||
|
||||
uint8_t* h[10];
|
||||
h[n] = hookhash;
|
||||
|
||||
uint8_t emit_buf[1024];
|
||||
uint32_t emit_size = 0;
|
||||
PREPARE_HOOKSET(emit_buf, sizeof(emit_buf), h, emit_size);
|
||||
|
||||
trace(SBUF("EmittedTxn"), emit_buf, emit_size, 1);
|
||||
|
||||
uint8_t emithash[32];
|
||||
int64_t emit_result = emit(SBUF(emithash), emit_buf, emit_size);
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(emit_result);
|
||||
|
||||
if (emit_result != 32)
|
||||
NOPE("Governance: Emit failed during hook actioning.");
|
||||
|
||||
trace(SBUF("EmittedTxnHash"), emithash, 32, 1);
|
||||
DONE("Governance: Hook actioned.");
|
||||
}
|
||||
|
||||
case 'S':
|
||||
{
|
||||
// add / change member
|
||||
uint8_t previous_member[32];
|
||||
int previous_present = (state(previous_member + 12, 20, &n, 1) == 20);
|
||||
if (previous_present)
|
||||
{
|
||||
trace(SBUF("Previous present==:"), previous_member, 32, 1);
|
||||
}
|
||||
|
||||
|
||||
if (BUFFER_EQUAL_20((previous_member + 12), (topic_data + 12)))
|
||||
DONE("Governance: Actioning seat change, but seat already contains the new member.");
|
||||
|
||||
int64_t existing_member = state(0,0, topic_data + 12, 20);
|
||||
|
||||
int existing_member_moving = existing_member >= 0;
|
||||
if (existing_member_moving)
|
||||
trace(SBUF("Governance: Moving existing member to new seat."), 0,0,0);
|
||||
|
||||
|
||||
uint8_t op = ((!previous_present) << 2U) +
|
||||
(topic_data_zero << 1U) + existing_member_moving;
|
||||
|
||||
ASSERT(op != 0b011U && op != 0b111U && op < 8);
|
||||
|
||||
// logic table:
|
||||
// E/!E - seat is empty/filled
|
||||
// Z/!Z - topic data is zero non zero (zero = member deletion)
|
||||
// M/!M - topic is an existing member who is moving
|
||||
//
|
||||
// E|Z|M
|
||||
// -+-+-
|
||||
// 0 0 0 - seat is full, vote is for a member, an existing member is not moving MC
|
||||
// 0 0 1 - seat is full, vote is for a member, an existing member is moving MC--
|
||||
// 0 1 0 - seat is full, vote is for deletion, an existing member is not moving MC--
|
||||
// 0 1 1 - seat is full, vote is for deletion, an existing member is moving (impossible)
|
||||
// 1 0 0 - seat is empty, vote is for a member, member is not an existing member MC++
|
||||
// 1 0 1 - seat is empty, vote is for a member, member is an existing member MC
|
||||
// 1 1 0 - seat is empty, vote is for deletion, not an existing member moving MC
|
||||
// 1 1 1 - seat is empty, vote is for deletion, an existing member moving (impossible)
|
||||
|
||||
TRACEVAR(op);
|
||||
trace_num(SBUF("E"), !previous_present);
|
||||
trace_num(SBUF("Z"), topic_data_zero);
|
||||
trace_num(SBUF("M"), existing_member_moving);
|
||||
|
||||
// adjust member count
|
||||
{
|
||||
if (op == 0b001U || op == 0b010U)
|
||||
member_count--;
|
||||
else if (op == 0b100U)
|
||||
member_count++;
|
||||
|
||||
TRACEVAR(previous_present);
|
||||
TRACEVAR(topic_data_zero);
|
||||
TRACEVAR(member_count);
|
||||
|
||||
ASSERT(member_count > 1); // just bail out if the second last member is being removed
|
||||
|
||||
uint8_t mc = member_count;
|
||||
ASSERT(state_set(&mc, 1, "MC", 2) == 1);
|
||||
}
|
||||
|
||||
// if an existing member is moving we need to delete them before re-adding them
|
||||
if (existing_member_moving)
|
||||
{
|
||||
|
||||
// delete the old member
|
||||
// reverse key
|
||||
uint8_t m = (uint8_t)existing_member;
|
||||
ASSERT(state_set(0,0, &m, 1) == 0);
|
||||
|
||||
// forward key
|
||||
ASSERT(state_set(0, 0, topic_data + 12, 20) == 0);
|
||||
|
||||
}
|
||||
|
||||
// we need to garbage collect all their votes
|
||||
if (previous_present)
|
||||
{
|
||||
previous_member[0] = 'V';
|
||||
|
||||
for (int i = 1; GUARD(32), i < 32; ++i)
|
||||
{
|
||||
previous_member[1] = i < 2 ? 'R' : i < 12 ? 'H' : 'S';
|
||||
previous_member[2] =
|
||||
i == 0 ? 'R' :
|
||||
i == 1 ? 'D' :
|
||||
i < 12 ? i - 2 :
|
||||
i - 12;
|
||||
|
||||
uint8_t vote_key[32];
|
||||
if (state(SBUF(vote_key), SBUF(previous_member)) == 32)
|
||||
{
|
||||
uint8_t vote_count = 0;
|
||||
|
||||
// find and decrement the vote counter
|
||||
vote_key[0] = 'C';
|
||||
vote_key[1] = previous_member[1];
|
||||
vote_key[2] = previous_member[2];
|
||||
if (state(&vote_count, 1, SBUF(vote_key)) == 1)
|
||||
{
|
||||
// if we're down to 1 vote then delete state
|
||||
if (vote_count <= 1)
|
||||
{
|
||||
ASSERT(state_set(0,0, SBUF(vote_key)) == 0);
|
||||
trace_num(SBUF("Decrement vote count deleted"), vote_count);
|
||||
}
|
||||
else // otherwise decrement
|
||||
{
|
||||
vote_count--;
|
||||
ASSERT(state_set(&vote_count, 1, SBUF(vote_key)) == 1);
|
||||
trace_num(SBUF("Decrement vote count to"), vote_count);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the vote entry
|
||||
ASSERT(state_set(0,0, SBUF(previous_member)) == 0);
|
||||
trace(SBUF("Vote entry deleted"), vote_key, 32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the old member
|
||||
// reverse key
|
||||
ASSERT(state_set(0,0, &n, 1) == 0);
|
||||
|
||||
// forward key
|
||||
ASSERT(state_set(0, 0, previous_member + 12, 20) == 0);
|
||||
}
|
||||
|
||||
if (!topic_data_zero)
|
||||
{
|
||||
// add the new member
|
||||
// reverse key
|
||||
ASSERT(state_set(topic_data + 12, 20, &n, 1) == 20);
|
||||
|
||||
// forward key
|
||||
ASSERT(state_set(&n, 1, topic_data + 12, 20) == 1);
|
||||
}
|
||||
|
||||
DONE("Governance: Action member change.");
|
||||
}
|
||||
}
|
||||
|
||||
rollback(SBUF("Governance: Internal logic error."), __LINE__);
|
||||
}
|
||||
146
hook/genesis/makefile
Normal file
146
hook/genesis/makefile
Normal file
@@ -0,0 +1,146 @@
|
||||
all: reward govern mint
|
||||
accept:
|
||||
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I../
|
||||
hook-cleaner accept.wasm
|
||||
reward:
|
||||
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt reward.wasm -o reward.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 reward.wasm
|
||||
wasm-opt reward.wasm -o reward.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 reward.wasm
|
||||
guard_checker reward.wasm
|
||||
govern:
|
||||
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt govern.wasm -o govern.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 govern.wasm
|
||||
wasm-opt govern.wasm -o govern.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 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
|
||||
guard_checker mint.wasm
|
||||
nftoken:
|
||||
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I../
|
||||
hook-cleaner nftoken.wasm
|
||||
123
hook/genesis/mint.c
Normal file
123
hook/genesis/mint.c
Normal file
@@ -0,0 +1,123 @@
|
||||
// This hook just tests GenesisMint transactor, it is not for production use
|
||||
|
||||
#include "hookapi.h"
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback(SBUF("MintTest: Assertion failure."),__LINE__);
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
uint8_t txn_mint[60000] =
|
||||
{
|
||||
/* 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
|
||||
*
|
||||
* */
|
||||
// 210 bytes + 34 bytes per entry * number of entries + any alignment padding desired
|
||||
};
|
||||
|
||||
#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);
|
||||
|
||||
hook_account(txn_mint + 71, 20);
|
||||
|
||||
otxn_slot(1);
|
||||
ASSERT(slot_subfield(1, sfBlob, 2) == 2);
|
||||
|
||||
|
||||
int64_t bytes = slot(txn_mint + 207, 60000 - 207, 2);
|
||||
|
||||
ASSERT(bytes > 0);
|
||||
|
||||
bytes += 207;
|
||||
|
||||
// nop out any vl encoding
|
||||
uint8_t x = txn_mint[207];
|
||||
uint8_t y = txn_mint[208];
|
||||
uint8_t z = txn_mint[209];
|
||||
|
||||
txn_mint[207] = 0x99U;
|
||||
if (x > 192U)
|
||||
{
|
||||
txn_mint[208] = 0x99U;
|
||||
if (x > 240U)
|
||||
txn_mint[209] = 0x99U;
|
||||
}
|
||||
|
||||
|
||||
trace(SBUF("Txn:"), txn_mint, bytes, 1);
|
||||
|
||||
ASSERT(etxn_details(txn_mint + 91, 116) > 0);
|
||||
|
||||
int64_t fee = etxn_fee_base(txn_mint, bytes);
|
||||
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, bytes, 1);
|
||||
|
||||
uint8_t emithash[32];
|
||||
int64_t emit_result = emit(SBUF(emithash), txn_mint, bytes);
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(emit_result);
|
||||
|
||||
if (emit_result < 0)
|
||||
rollback(SBUF("MintTest: Emit failed."), __LINE__);
|
||||
|
||||
|
||||
accept(SBUF("MintTest: Emitted txn successfully."), __LINE__);
|
||||
}
|
||||
213
hook/genesis/nftoken.c
Normal file
213
hook/genesis/nftoken.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <stdint.h>
|
||||
#include "hookapi.h"
|
||||
#define SVAR(x) &x, sizeof(x)
|
||||
#define SBUF(x) (x), sizeof(x)
|
||||
#define NOPE(x)\
|
||||
{\
|
||||
return rollback((x), sizeof(x), __LINE__);\
|
||||
}
|
||||
|
||||
#define ttNFTOKEN_BURN 26
|
||||
#define ttIMPORT 97
|
||||
#define ttURITOKEN_MINT 45
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t hook_acc[20];
|
||||
hook_account(SBUF(hook_acc));
|
||||
|
||||
uint8_t otxn_acc[20];
|
||||
otxn_field(SBUF(otxn_acc), sfAccount);
|
||||
|
||||
// outgoing
|
||||
if (BUFFER_EQUAL_20(hook_acc, otxn_acc))
|
||||
accept(SBUF("NFTImport: Passing outgoing txn."), __LINE__);
|
||||
|
||||
if (otxn_type() != ttIMPORT)
|
||||
accept(SBUF("NFTImport: Passing non ttIMPORT txn."), otxn_type());
|
||||
|
||||
int64_t retval = xpop_slot(1,2);
|
||||
|
||||
if (retval <= 0)
|
||||
NOPE("Failed to slot xpop");
|
||||
|
||||
#define tx_slot 1
|
||||
#define meta_slot 2
|
||||
|
||||
trace_num("Slotted xpop", 12, retval);
|
||||
uint8_t dump1[2048];
|
||||
uint8_t dump2[2048];
|
||||
int64_t len1 = slot(dump1, sizeof(dump1), tx_slot);
|
||||
int64_t len2 = slot(dump2, sizeof(dump2), meta_slot);
|
||||
|
||||
trace("tx", 2, dump1, len1, 1);
|
||||
trace("meta", 4, dump2, len2, 1);
|
||||
|
||||
|
||||
if (slot_subfield(meta_slot, sfTransactionResult, 3) != 3)
|
||||
NOPE("Failed to slot transaction result");
|
||||
|
||||
uint8_t tr;
|
||||
if (slot(SVAR(tr), 3) != 1)
|
||||
NOPE("Failed to dump transaction result");
|
||||
|
||||
trace_num(SBUF("Inner Transaction Result:"), tr);
|
||||
if (tr != 0)
|
||||
NOPE("Inner Transaction Result not tesSUCCESS (0).");
|
||||
|
||||
// execution to here means tesSUCCESS on inner
|
||||
|
||||
if (slot_subfield(tx_slot, sfTransactionType, 4) != 4)
|
||||
NOPE("Could not slot transaction type");
|
||||
|
||||
uint8_t tt_buf[2];
|
||||
if (slot(SBUF(tt_buf), 4) != 2)
|
||||
NOPE("Could not dump transaction type");
|
||||
|
||||
uint16_t tt = UINT16_FROM_BUF(tt_buf);
|
||||
|
||||
if (tt != ttNFTOKEN_BURN)
|
||||
NOPE("Only NFTokenBurn is accepted");
|
||||
|
||||
// go track down the URI of the token (this is a huge pain, has to be done through metadata)
|
||||
//
|
||||
#define nodes 5
|
||||
if (slot_subfield(meta_slot, sfAffectedNodes, nodes) != nodes)
|
||||
NOPE("Could not slot sfAffectedNodes");
|
||||
|
||||
uint8_t dump4[1024];
|
||||
trace(SBUF("slot nodes"), dump4, slot(SBUF(dump4), nodes), 1);
|
||||
|
||||
|
||||
int64_t count = slot_count(nodes);
|
||||
if (count > 5) count = 5;
|
||||
|
||||
int64_t found;
|
||||
|
||||
for (int i = 0; GUARD(5), i < count; ++i)
|
||||
{
|
||||
if (slot_subarray(nodes, i, 6) != 6)
|
||||
break;
|
||||
|
||||
if (slot_subfield(6, sfLedgerEntryType, 7) != 7)
|
||||
NOPE("Could not slot LedgerEntryType");
|
||||
|
||||
uint8_t buf[2];
|
||||
slot(SVAR(buf), 7);
|
||||
|
||||
if (UINT16_FROM_BUF(buf) == 0x0050U)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
NOPE("Could not find NFTokenPage in xpop metadata");
|
||||
|
||||
if (slot_subfield(6, sfPreviousFields, 6) != 6 && slot_subfield(6, sfFinalFields, 6) != 6)
|
||||
NOPE("Could not slot sfPreviousFields");
|
||||
|
||||
if (slot_subfield(6, sfNFTokens, 6) != 6)
|
||||
NOPE("Could not slot sfNFTokens");
|
||||
|
||||
count = slot_count(6);
|
||||
if (count > 32) count = 32;
|
||||
|
||||
trace_num(SBUF("Modified node count:"), count);
|
||||
uint8_t burned_tid[32];
|
||||
if (slot_subfield(tx_slot, sfNFTokenID, 7) != 7 || slot(SBUF(burned_tid), 7) != 32)
|
||||
NOPE("Xpop txn did not contain valid nftokenid");
|
||||
|
||||
uint8_t uri[256];
|
||||
int64_t urilen;
|
||||
|
||||
found = 0;
|
||||
for (int i = 0; GUARD(32), i < count; ++i)
|
||||
{
|
||||
if (slot_subarray(6, i, 7) != 7)
|
||||
break;
|
||||
|
||||
{
|
||||
uint8_t dump4[1024];
|
||||
int64_t len = slot(SBUF(dump4), 7);
|
||||
trace(SBUF("dump4"), dump4, len, 1);
|
||||
}
|
||||
|
||||
uint8_t tid[32];
|
||||
int64_t r;
|
||||
if ((r=slot_subfield(7, sfNFTokenID, 8)) != 8)
|
||||
{
|
||||
trace_num(SBUF("r"), r);
|
||||
break;
|
||||
}
|
||||
|
||||
slot(SBUF(tid), 8);
|
||||
|
||||
if (slot_subfield(7, sfURI, 8) != 8)
|
||||
continue;
|
||||
|
||||
urilen = slot(SBUF(uri), 8);
|
||||
|
||||
trace(SBUF("uri: "), uri, urilen, 1);
|
||||
trace(SBUF("tid1"), tid, 32, 1);
|
||||
trace(SBUF("tid2"), burned_tid, 32, 1);
|
||||
found = BUFFER_EQUAL_32(tid, burned_tid);
|
||||
if (found)
|
||||
{
|
||||
trace(SBUF("found"), 0,0,0);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
NOPE("Could not find the NFTokenID in the metadata");
|
||||
|
||||
trace(SBUF("URI from xpop: "), uri, urilen, 1);
|
||||
|
||||
etxn_reserve(1);
|
||||
uint8_t txn_buf[1024];
|
||||
int64_t txn_len;
|
||||
{
|
||||
uint8_t* buf_out = txn_buf;
|
||||
uint32_t cls = (uint32_t)ledger_seq();
|
||||
_01_02_ENCODE_TT (buf_out, ttURITOKEN_MINT );
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL );
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 0 );
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 1 );
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 5 );
|
||||
_06_01_ENCODE_DROPS_AMOUNT (buf_out, 0 );
|
||||
uint8_t* fee_ptr = buf_out;
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0 );
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out );
|
||||
|
||||
// URI
|
||||
*buf_out++ = 0x75U;
|
||||
for (int i = 0; GUARD(32), i < 32; ++i)
|
||||
*(((uint64_t*)buf_out) + i) = *(((uint64_t*)uri) + i);
|
||||
buf_out += urilen;
|
||||
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, hook_acc );
|
||||
_08_03_ENCODE_ACCOUNT_DST (buf_out, otxn_acc );
|
||||
int64_t edlen = etxn_details((uint32_t)buf_out, 512);
|
||||
trace_num(SBUF("edlen"), edlen);
|
||||
buf_out += edlen;
|
||||
txn_len = buf_out - txn_buf;
|
||||
int64_t fee = etxn_fee_base(txn_buf, txn_len);
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee );
|
||||
|
||||
}
|
||||
|
||||
trace(SBUF("emit txn"), txn_buf, txn_len, 1);
|
||||
|
||||
uint8_t etxid[32];
|
||||
if (emit(SBUF(etxid), txn_buf, txn_len) < 0)
|
||||
NOPE("Emission failed");
|
||||
|
||||
trace(SBUF("Emission success"), etxid, 32, 1);
|
||||
|
||||
return accept(0UL, 0UL, __LINE__);
|
||||
}
|
||||
69
hook/genesis/rekey-validator.js
Normal file
69
hook/genesis/rekey-validator.js
Normal file
@@ -0,0 +1,69 @@
|
||||
//reworked version of https://github.com/RichardAH/xrpl-tools/blob/master/validator-address-tool/vatool.js
|
||||
const rac = require('ripple-address-codec');
|
||||
const { XrplClient } = require('xrpl-client');
|
||||
const { sign, derive, XrplDefinitions } = require('xrpl-accountlib');
|
||||
const ed = require('elliptic').eddsa;
|
||||
const ec = new ed('ed25519');
|
||||
|
||||
if (process.argv.length < 6)
|
||||
{
|
||||
console.log("Rekey validator address tool");
|
||||
console.log("Usage: node " + process.argv[1] + " validator_secret_key raddr_to_rekey_to fee_drops network_id [ws://localhost:6005]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sk = (()=>
|
||||
{
|
||||
return 'ED' +
|
||||
Buffer.from(ec.keyFromSecret(rac.codec._codec.decode(process.argv[2]).slice(1,33)).secret()).
|
||||
toString('hex').toUpperCase();
|
||||
})();
|
||||
|
||||
|
||||
console.log(sk)
|
||||
const account = derive.privatekey(sk);
|
||||
console.log(account);
|
||||
|
||||
const endpoint = process.argv.length >= 7 ? process.argv[6] : 'ws://localhost:6005';
|
||||
console.log("endpoint:", endpoint);
|
||||
const client = new XrplClient(endpoint);
|
||||
|
||||
(async () => {
|
||||
const liveDefinitions = await client.send({ "command": "server_definitions" })
|
||||
|
||||
const definitions = new XrplDefinitions(liveDefinitions)
|
||||
|
||||
const ledgerInfo = await client.send({
|
||||
command: 'ledger'
|
||||
})
|
||||
|
||||
// console.log(ledgerInfo)
|
||||
|
||||
const accountInfo = await client.send({
|
||||
command: 'account_info',
|
||||
account: account.address,
|
||||
})
|
||||
|
||||
const txJsonPreSigning = {
|
||||
TransactionType: 'SetRegularKey',
|
||||
Account: account.address,
|
||||
RegularKey: process.argv[3],
|
||||
Fee: process.argv[4] + '',
|
||||
LastLedgerSequence: Number(ledgerInfo.closed.ledger.seqNum) + 5,
|
||||
Sequence: accountInfo.account_data.Sequence,
|
||||
NetworkID: process.argv[5]
|
||||
};
|
||||
|
||||
const signed = sign(txJsonPreSigning, account, definitions);
|
||||
|
||||
console.log('Tx', signed.id);
|
||||
console.log(signed.signedTransaction);
|
||||
|
||||
const submit = await client.send({
|
||||
command: 'submit',
|
||||
tx_blob: signed.signedTransaction
|
||||
});
|
||||
|
||||
console.log(submit);
|
||||
})();
|
||||
|
||||
348
hook/genesis/reward.c
Normal file
348
hook/genesis/reward.c
Normal file
@@ -0,0 +1,348 @@
|
||||
#include "hookapi.h"
|
||||
#define DEFAULT_REWARD_DELAY 6199553087261802496ULL
|
||||
// 2600000
|
||||
#define DEFAULT_REWARD_RATE 6038156834009797973ULL
|
||||
// 0.00333333333f
|
||||
|
||||
#define L1SEATS 20U
|
||||
#define MAXUNL 128U
|
||||
#define SVAR(x) &(x), sizeof(x)
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback(SBUF("Reward: Assertion failure."),__LINE__);
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
uint8_t txn_mint[928] =
|
||||
{
|
||||
/* 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
|
||||
*
|
||||
* */
|
||||
// 210 bytes + 34 bytes per entry * number of entries + any alignment padding desired
|
||||
};
|
||||
|
||||
uint8_t template[40] = {
|
||||
/* 0, 2 */ 0xE0U, 0x60U, // obj start
|
||||
/* 2, 1 */ 0x61U, // amount header
|
||||
/* 3, 8 */ 0,0,0,0,0,0,0,0, // amount payload
|
||||
/* 11, 2 */ 0x83U, 0x14U, // account header
|
||||
/* 13, 20 */ 0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0, // account payload
|
||||
/* 33, 1 */ 0xE1U // obj end
|
||||
};
|
||||
|
||||
|
||||
#define TEMPLATE_DROPS(drops_tmp)\
|
||||
{\
|
||||
uint8_t* b = template + 3U;\
|
||||
*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;\
|
||||
}
|
||||
|
||||
#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;\
|
||||
}
|
||||
|
||||
uint8_t member_count_key[2] = {'M', 'C'};
|
||||
|
||||
|
||||
uint8_t unlreport_keylet[34] =
|
||||
{
|
||||
0x00U,0x52U,0x61U,0xE3U,0x2EU,0x7AU,0x24U,0xA2U,0x38U,
|
||||
0xF1U,0xC6U,0x19U,0xD5U,0xF9U,0xDDU,0xCCU,0x41U,0xA9U,
|
||||
0x4BU,0x33U,0xB6U,0x6CU,0x01U,0x63U,0xF7U,0xEFU,0xCCU,
|
||||
0x8AU,0x19U,0xC9U,0xFDU,0x6FU,0x28U,0xDCU
|
||||
};
|
||||
|
||||
uint8_t msg_buf[] = "You must wait 0000000 seconds";
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
etxn_reserve(1);
|
||||
_g(1,1);
|
||||
|
||||
// only process ttCLAIM_REWARD
|
||||
if (otxn_type() != 98)
|
||||
accept(SBUF("Reward: Passing non-claim txn"), __LINE__);
|
||||
|
||||
uint8_t otxn_acc[20];
|
||||
uint8_t hook_acc[20];
|
||||
|
||||
// get the account id
|
||||
otxn_field(SBUF(otxn_acc), sfAccount);
|
||||
|
||||
// write the hook account into the txn template
|
||||
hook_account(SBUF(hook_acc));
|
||||
|
||||
if (BUFFER_EQUAL_20(hook_acc, otxn_acc))
|
||||
accept(SBUF("Reward: Passing outgoing txn"), __LINE__);
|
||||
|
||||
// the default rate and delay are used if somehow the keys are missing from hook state (graceful failure)
|
||||
int64_t xfl_rr = DEFAULT_REWARD_RATE;
|
||||
int64_t xfl_rd = DEFAULT_REWARD_DELAY;
|
||||
|
||||
// load state if it exists (which it should)
|
||||
state(&xfl_rr, 8, "RR", 2);
|
||||
state(&xfl_rd, 8, "RD", 2);
|
||||
|
||||
// if either of these is 0 that's disabled
|
||||
if (xfl_rr <= 0 || xfl_rd <= 0 )
|
||||
rollback(SBUF("Reward: Rewards are disabled by governance."), __LINE__);
|
||||
|
||||
int64_t required_delay = float_int(xfl_rd, 0, 0);
|
||||
|
||||
if (required_delay < 0 || float_sign(xfl_rr) != 0 ||
|
||||
float_compare(xfl_rr, float_one(), COMPARE_GREATER) ||
|
||||
float_compare(xfl_rd, float_one(), COMPARE_LESS))
|
||||
rollback(SBUF("Reward: Rewards incorrectly configured by governance or unrecoverable error."), __LINE__);
|
||||
|
||||
// get the account root keylet
|
||||
uint8_t kl[34];
|
||||
util_keylet(SBUF(kl), KEYLET_ACCOUNT, SBUF(otxn_acc), 0,0,0,0);
|
||||
|
||||
// slot the account root, this can't fail
|
||||
slot_set(SBUF(kl), 1);
|
||||
|
||||
// this is a first time claim reward has run and will setup these fields
|
||||
if (slot_subfield(1, sfRewardAccumulator, 2) != 2)
|
||||
accept(SBUF("Reward: Passing reward setup txn"), __LINE__);
|
||||
|
||||
// this is an actual claim reward
|
||||
slot_subfield(1, sfRewardLgrFirst, 3);
|
||||
slot_subfield(1, sfRewardLgrLast, 4);
|
||||
slot_subfield(1, sfBalance, 5);
|
||||
slot_subfield(1, sfRewardTime, 6);
|
||||
|
||||
int64_t time = slot(0,0,6);
|
||||
|
||||
int64_t time_elapsed = ledger_last_time() - time;
|
||||
|
||||
|
||||
if (time_elapsed < required_delay)
|
||||
{
|
||||
//2 600 000
|
||||
time_elapsed = required_delay - time_elapsed;
|
||||
msg_buf[14] += (time_elapsed / 1000000) % 10;
|
||||
msg_buf[15] += (time_elapsed / 100000) % 10;
|
||||
msg_buf[16] += (time_elapsed / 10000) % 10;
|
||||
msg_buf[17] += (time_elapsed / 1000) % 10;
|
||||
msg_buf[18] += (time_elapsed / 100) % 10;
|
||||
msg_buf[19] += (time_elapsed / 10) % 10;
|
||||
msg_buf[20] += (time_elapsed ) % 10;
|
||||
|
||||
rollback(SBUF(msg_buf), __LINE__);
|
||||
}
|
||||
|
||||
int64_t accumulator = slot(0,0,2);
|
||||
int64_t first = slot(0,0,3);
|
||||
int64_t last = slot(0,0,4);
|
||||
int64_t bal = slot(0,0,5);
|
||||
|
||||
if (DEBUG)
|
||||
{
|
||||
TRACEVAR(accumulator);
|
||||
TRACEVAR(first);
|
||||
TRACEVAR(last);
|
||||
}
|
||||
|
||||
ASSERT(/*accumulator > 0 &&*/ first > 0 && last > 0);
|
||||
|
||||
// we need to add the final block ourselves
|
||||
|
||||
int64_t cur = ledger_seq();
|
||||
|
||||
int64_t elapsed = cur - first;
|
||||
|
||||
ASSERT(elapsed > 0);
|
||||
|
||||
int64_t elapsed_since_last = ledger_seq() - last;
|
||||
|
||||
bal &= ~0xE000000000000000ULL;
|
||||
bal /= 1000000LL;
|
||||
|
||||
if (DEBUG)
|
||||
{
|
||||
TRACEVAR(bal);
|
||||
TRACEVAR(accumulator);
|
||||
}
|
||||
|
||||
if (bal > 0 && elapsed_since_last > 0)
|
||||
accumulator += bal * elapsed_since_last;
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(accumulator);
|
||||
|
||||
|
||||
int64_t xfl_accum = float_set(0, accumulator);
|
||||
ASSERT(xfl_accum > 0);
|
||||
|
||||
int64_t xfl_elapsed = float_set(0, elapsed);
|
||||
ASSERT(xfl_elapsed > 0);
|
||||
|
||||
int64_t xfl_reward = float_divide(xfl_accum, xfl_elapsed);
|
||||
xfl_reward = float_multiply(xfl_rr, xfl_reward);
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(xfl_reward);
|
||||
|
||||
|
||||
uint64_t reward_drops = float_int(xfl_reward, 6, 1);
|
||||
|
||||
uint64_t l1_drops = reward_drops / L1SEATS;
|
||||
|
||||
int64_t otxn_slot_num = otxn_slot(10);
|
||||
ASSERT(otxn_slot_num == 10);
|
||||
|
||||
int64_t fee_slot_num = slot_subfield(10, sfFee, 11);
|
||||
ASSERT(fee_slot_num == 11);
|
||||
|
||||
int64_t xfl_fee = slot_float(11);
|
||||
|
||||
// user gets back the fee they spent running the hook
|
||||
if (xfl_fee > 0)
|
||||
reward_drops += float_int(xfl_fee, 6, 1);
|
||||
|
||||
TEMPLATE_DROPS(reward_drops);
|
||||
|
||||
uint8_t* upto = txn_mint + 209U;
|
||||
uint8_t* end = upto + (34U * (L1SEATS + 1));
|
||||
|
||||
// first account is always the rewardee
|
||||
{
|
||||
uint64_t* d = (uint64_t*)upto;
|
||||
uint64_t* s = (uint64_t*)template;
|
||||
*d++ = *s++;
|
||||
*d++ = *s++;
|
||||
*(d+2) = *(s+2);
|
||||
otxn_field(upto + 13, 20, sfAccount);
|
||||
}
|
||||
upto += 34U;
|
||||
|
||||
// now iterate all possible seats in all possible tables
|
||||
TEMPLATE_DROPS(l1_drops);
|
||||
|
||||
|
||||
// there are two conditions for L1 governance members to receive rewards:
|
||||
// 1. they must be an L1 member
|
||||
// 2. they must be an active validator
|
||||
|
||||
// load the UNLReport, this will let us know who has been validating and who hasn't
|
||||
|
||||
uint64_t can_reward[L1SEATS];
|
||||
|
||||
|
||||
uint8_t av_array[(60 * MAXUNL) + 4];
|
||||
if (slot_set(SBUF(unlreport_keylet), 1) == 1 &&
|
||||
slot_subfield(1, sfActiveValidators, 1) == 1 &&
|
||||
slot(SBUF(av_array), 1) > 0)
|
||||
{
|
||||
// at least some validators have been validating so those get a reward if they are on the governance table
|
||||
// we are going to assume the UNL never exceeds 64
|
||||
uint8_t seat = 0;
|
||||
uint8_t* av_upto = av_array + 39 /* offset to the first key */;
|
||||
uint64_t av_size = slot_count(1);
|
||||
if (av_size > MAXUNL) av_size = MAXUNL;
|
||||
for (uint64_t i = 0; GUARD(MAXUNL), i < av_size; ++i, av_upto += 60U)
|
||||
{
|
||||
trace(SBUF("av:"), av_upto, 20, 1);
|
||||
if (state(SVAR(seat), av_upto, 20) != 1 || seat > L1SEATS)
|
||||
continue;
|
||||
can_reward[seat] = 1;
|
||||
}
|
||||
|
||||
// iterate the seats at the table and add reward entries for the active validators
|
||||
for (uint8_t l1_seat = 0; upto < end && l1_seat < L1SEATS; l1_seat++)
|
||||
{
|
||||
GUARD(L1SEATS);
|
||||
if (!can_reward[l1_seat])
|
||||
continue;
|
||||
|
||||
// copy template 1 into next GenesisMints array position
|
||||
uint64_t* d = (uint64_t*)upto;
|
||||
uint64_t* s = (uint64_t*)template;
|
||||
*d++ = *s++;
|
||||
*d++ = *s++;
|
||||
*(d+2) = *(s+2);
|
||||
|
||||
if (state(upto + 13, 20, &l1_seat, 1) == 20)
|
||||
upto += 34;
|
||||
}
|
||||
|
||||
}
|
||||
*upto++ = 0xF1U;
|
||||
|
||||
|
||||
// populate other txn fields
|
||||
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("Reward: Emit loopback failed."), __LINE__);
|
||||
|
||||
|
||||
accept(SBUF("Reward: Emitted reward txn successfully."), __LINE__);
|
||||
}
|
||||
Reference in New Issue
Block a user