diff --git a/hook/govern.c b/hook/govern.c index 5035c883f..f766ae69e 100644 --- a/hook/govern.c +++ b/hook/govern.c @@ -16,11 +16,9 @@ * 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 this same governance hook installed on it and is blackholed. + * 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. - * The same voting rules apply but the voting threshold is 60% for everything. - * At L2, a successful vote is actioned by having the L1 seat that L2 table occupies cast an L1 vote. * * Hook Parameters: * Parameter Name: {'I', 'M', 'C'} @@ -63,7 +61,7 @@ * State Key: {'V', 'H|R|S' , '\0 + topic id', 0..0, } * State Data: A vote by a member for a topic and topic data * - * State Key: {'C', 'H|h|R|S' , '\0 + topic id', 0*, } + * State Key: {'C', 'H|R|S' , '\0 + topic id', 0*, } * State Data: The number of members who have voted for that topic data and topic combination <1 byte> * * Hook Invocation: @@ -76,7 +74,7 @@ * * Parameter Name: {'T'} * Parameter Value: The topic to vote on <2 bytes> - * { 'S|s|H|h' (seat L1, seat L2, hook L1, hook L2), '\0 + topic id' }, or + * { 'S|H' (seat L1, hook L1), '\0 + topic id' }, or * { 'R' (reward), 'R|D' (rate, delay) } * * Parameter Name: {'V'} @@ -126,11 +124,14 @@ int64_t hook(uint32_t r) int64_t is_L1 = BUFFER_EQUAL_20(hook_accid + 12, genesis); + if (!is_L1) + NOPE("Governance: This is the L1 governance hook. It can only be used on genesis account."); + + int64_t member_count = state(0,0, "MC", 2); if (DEBUG) TRACEVAR(member_count); - // initial execution, setup hook if (member_count == DOESNT_EXIST) { @@ -146,29 +147,24 @@ int64_t hook(uint32_t r) if (imc > SEAT_COUNT) NOPE("Governance: Initial Member Count must be <= Seat Count (20)."); - if (is_L1) - { - if (hook_param(SVAR(irr), "IRR", 3) < 0) - NOPE("Governance: Initial Reward Rate Parameter missing (IRR)."); + 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(state_set(SVAR(irr), "RR", 2)); - - // set reward delay - ASSERT(state_set(SVAR(ird), "RD", 2)); - } + 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(state_set(SVAR(irr), "RR", 2)); + // set reward delay + ASSERT(state_set(SVAR(ird), "RD", 2)); // set member count ASSERT(state_set(SBUF(imc), "MC", 2)); - member_count = imc; for (uint8_t i = 0; GUARD(SEAT_COUNT), i < member_count; ++i) @@ -209,47 +205,27 @@ int64_t hook(uint32_t r) if (result != 2 || ( t != 'S' && // topic type: seat (L1) - t != 's' && // topic type: seat (L2) t != 'H' && // topic type: hook (L1) - t != 'h' && // topic type: hook (L2) t != 'R')) // topic type: reward NOPE("Governance: Valid TOPIC must be specified as otxn parameter."); - if ((t == 'S' || t == 's') && n > (SEAT_COUNT - 1)) + if (t == 'S' && n > (SEAT_COUNT - 1)) NOPE("Governance: Valid seat topics are 0 through 19."); - if ((t == 'H' || t == 'h') && n > HOOK_MAX) + 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)."); - if (is_L1) - { - if (t == 'h' || t == 's') - NOPE("Governance: Cannot vote on topic `h` or `s` at L1 table (did you mean `H`/`S`?)."); - - // pass - } - else - if (t != 'h' && t != 's') - { - // check if this is a real L2 table (is it seated at an L1 seat?) - // only setup and voting on the local hook hash is allowed until it gets a seat - uint8_t dummy; - if (state_foreign(SVAR(dummy), hook_accid + 12, 20, SBUF(zero32), SBUF(genesis)) != 1) - NOPE("Governance: This L2 table is not seated at the L1 table, so only `h`/`s` topic voting is allowed."); - } // RH TODO: validate RR/RD xfl > 0 uint8_t topic_data[32]; uint8_t topic_size = t == 'H' ? 32 : // hook topics are a 32 byte hook hash - t == 'h' ? 32 : // hook topics are a 32 byte hook hash (L2) t == 'S' ? 20 : // account topics are a 20 byte account ID - t == 's' ? 20 : - 8; // reward topics are an 8 byte le xfl + 8; // reward topics are an 8 byte le xfl uint8_t padding = 32 - topic_size; @@ -265,7 +241,8 @@ int64_t hook(uint32_t r) // 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)); + 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 @@ -329,79 +306,15 @@ int64_t hook(uint32_t r) int64_t q80 = member_count * 0.8; - int64_t q66 = member_count * 0.66; - if (is_L1) - { - if (votes < - t == 'S' - ? q80 // L1s have 80% threshold for membership/seat voting - : member_count) // L1s have 100% threshold for all other voting + 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 recorded at L1. Not yet enough votes to action."); - } - else - { - // for the current topic have we already cast a vote to the L1 table previously? - // reuse hook_accid for this - // - if (t == 'h') - { - // local voting topics - if (votes < member_count) - DONE("Governance: Vote recorded at L2. Not yet enough votes to action (100% needed)."); - t = 'H'; - // otherwise fall through - } - else if (t == 's') - { - if (votes < q80) - DONE("Governance: Vote recorded at L2. Not yet enough votes to action (80% needed)."); - t = 'S'; - // otherwise fall through - } - else - { - // the vote is for an L1 topic. we need to check what vote might have already been registered - // at the L1 table - hook_accid[0] = 'V'; - hook_accid[1] = t; - hook_accid[2] = n; - - uint8_t previous_topic_data_l1[32]; - - int64_t previous_topic_size_l1 = - state_foreign(SBUF(previous_topic_data_l1), SBUF(hook_accid), SBUF(zero32), SBUF(genesis)); - if (previous_topic_size_l1 == topic_size && BUFFER_EQUAL_32(previous_topic_data_l1, topic_data)) - DONE("Governance: Your whole-table's L1 vote is already cast this way for this topic."); - - int64_t previous_topic_data_l1_zero = - BUFFER_EQUAL_32(previous_topic_data_l1, zero32); - - // we need to check if this vote represents a lost majority status on the existing L1 topic data - previous_topic_data_l1[0] = 'C'; - previous_topic_data_l1[1] = t; - previous_topic_data_l1[2] = n; - - uint8_t existing_votes; - state(SVAR(existing_votes), SBUF(previous_topic_data_l1)); - - if (existing_votes < q66 && !previous_topic_data_l1_zero || votes >= q66) - { - // vacate the current vote at L1 - uint8_t* td = votes < q66 ? zero32 : topic_data; - PREPARE_GENESIS_INVOKE(t,n, td); // rh upto - DONE( - votes < q66 - ? "Governance: Previous vote vacated at L1, vote recorded at L2. Not enough votes to action." - : "Governance: Actioning vote at L1."); - } - else - DONE("Governance: Vote recorded at L2. Not yet enough votes to action."); - } - // action vote - if (DEBUG) TRACESTR("Actioning votes"); diff --git a/hook/reward.c b/hook/reward.c index a57432747..09bd9332e 100644 --- a/hook/reward.c +++ b/hook/reward.c @@ -1,11 +1,12 @@ #include "hookapi.h" //#define REWARD_DELAY 2600000LL -#define REWARD_DELAY 60LL +#define REWARD_DELAY 5LL #define REWARD_MULTIPLIER_XFL 6038156834009797973ULL //0.00333333333f #define L1SEATS 20U -#define L2SEATS 400U +#define MAXUNL 128U +#define SVAR(x) &(x), sizeof(x) #define ASSERT(x)\ if (!(x))\ @@ -13,7 +14,7 @@ #define DEBUG 1 -uint8_t txn_mint[13850] = +uint8_t txn_mint[928] = { /* size,upto */ /* 3, 0 */ 0x12U, 0x00U, 0x60U, /* tt = GenesisMint */ @@ -45,7 +46,7 @@ uint8_t txn_mint[13850] = // 210 bytes + 34 bytes per entry * number of entries + any alignment padding desired }; -uint8_t template1[40] = { +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 @@ -55,32 +56,10 @@ uint8_t template1[40] = { /* 33, 1 */ 0xE1U // obj end }; -uint8_t template2[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 TEMPLATE1_DROPS(drops_tmp)\ +#define TEMPLATE_DROPS(drops_tmp)\ {\ - uint8_t* b = template1 + 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 TEMPLATE2_DROPS(drops_tmp)\ -{\ - uint8_t* b = template2 + 3U;\ + uint8_t* b = template + 3U;\ *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ *b++ = (drops_tmp >> 48) & 0xFFU;\ *b++ = (drops_tmp >> 40) & 0xFFU;\ @@ -108,6 +87,13 @@ uint8_t template2[40] = { uint8_t member_count_key[2] = {'M', 'C'}; +uint8_t unlreport_keylet[32] = +{ + 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) @@ -237,8 +223,6 @@ int64_t hook(uint32_t r) uint64_t l1_drops = reward_drops / L1SEATS; - uint64_t l2_drops = reward_drops / L2SEATS; - otxn_slot(1); slot_subfield(1, sfFee, 2); @@ -249,76 +233,72 @@ int64_t hook(uint32_t r) reward_drops += float_int(xfl_fee, 6, 1); - TEMPLATE1_DROPS(reward_drops); + TEMPLATE_DROPS(reward_drops); uint8_t* upto = txn_mint + 209U; - uint8_t* end = upto + (34U * (L2SEATS + 1)); + uint8_t* end = upto + (34U * (L1SEATS + 1)); // first account is always the rewardee { uint64_t* d = (uint64_t*)upto; - uint64_t* s = (uint64_t*)template1; + 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 - TEMPLATE1_DROPS(l1_drops); - TEMPLATE2_DROPS(l2_drops); + TEMPLATE_DROPS(l1_drops); - uint8_t table[20]; - for (uint8_t l1_seat = 0, l2_seat = 0; GUARD(L2SEATS), upto < end && l1_seat < L1SEATS;) + // 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[(34 * MAXUNL) + 3]; + if (slot_set(SBUF(unlreport_keylet), 1) == 1 && + slot_subfield(1, sfActiveValidators, 1) == 1 && + slot(SBUF(av_array), 1) > 0) { - - if (l2_seat == 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 + 15 /* offset to the first account */; + 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) { + if (state(SVAR(seat), av_upto, 20) != 1 || seat > L1SEATS) + continue; + can_reward[seat] = 1; + av_upto += 34U; + } + + // iterate the seats at the table and add reward entries for the active validators + for (uint8_t l1_seat = 0; GUARD(L1SEATS), upto < end && l1_seat < 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*)template1; + uint64_t* s = (uint64_t*)template; *d++ = *s++; *d++ = *s++; *(d+2) = *(s+2); - // we're at an l1 seat in the iteration, check if it's filled and if so if it's filled with an L2 table if (state(upto + 13, 20, &l1_seat, 1) == 20) - { - // l1 seat is filled but by what? - state(SBUF(table), &l1_seat, 1); - l1_seat++; - - uint8_t dummy[1]; - if(state_foreign(SBUF(dummy), SBUF(member_count_key), SBUF(dummy), SBUF(table)) == DOESNT_EXIST) - { - // filled by a normal l1 member - upto += 34; - continue; - } - - // filled by an l2 table... fall through - } + upto += 34; } - // we're iterating l2 seats - // copy template2 into next GenesisMints array position - uint64_t* d = (uint64_t*)upto; - uint64_t* s = (uint64_t*)template2; - *d++ = *s++; - *d++ = *s++; - *(d+2) = *(s+2); - - uint8_t ns[32]; - if (state_foreign(upto + 13, 20, l2_seat, 1, SBUF(ns), SBUF(table)) == 20) - { - // filled l2 seat - upto += 34; - } - - l2_seat = (l2_seat + 1) % L1SEATS; } - *upto++ = 0xF1U; diff --git a/hook/sfcodes.h b/hook/sfcodes.h index 4b0339ece..86972c236 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -211,4 +211,5 @@ #define sfDisabledValidators ((15U << 16U) + 17U) #define sfHookExecutions ((15U << 16U) + 18U) #define sfHookParameters ((15U << 16U) + 19U) -#define sfHookGrants ((15U << 16U) + 20U) \ No newline at end of file +#define sfHookGrants ((15U << 16U) + 20U) +#define sfActiveValidators ((15U << 16U) + 95U)