-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Contract: Roles #131
base: hive-engine
Are you sure you want to change the base?
New Contract: Roles #131
Conversation
actions.createSSC = async () => { | ||
const tableExists = await api.db.tableExists('instances'); | ||
if (tableExists === false) { | ||
await api.db.createTable('instances', ['id', 'lastTickTime']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove 'id'
from ['id', 'lastTickTime']
If you were intending to add an index for the implicit MongoDB _id field (I'm assuming id without the underscore is a typo), there's always an index for that and you don't have to add it explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think we should be careful here, I've seen parts using .id vs ._id. It does seem like we want to rely on the auto index _id for the instance table.
} | ||
|
||
const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: voteToken }); | ||
if (!api.assert(voteTokenObj && voteTokenObj.stakingEnabled, 'voteToken must have staking enabled')) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should establish a limit for the # of instances to prevent mass staking events from taking too much time. We should probably also limit this to vote token issuers because of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely we can make the check about the number of active instances for a given token , and make sure we only do staking updates for active instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should ever allow there to be a potentially unbounded number of operations happening when there's a staking update, there should always be some well-defined upper limit that we are comfortable with. Otherwise there should be a mechanism to split update processing across multiple blocks, with intermediate state being stored between blocks.
if (processQueryLimit) { | ||
if (!api.assert(typeof processQueryLimit === 'string' && api.BigNumber(processQueryLimit).isInteger() && api.BigNumber(processQueryLimit).gte(1), 'invalid processQueryLimit')) return; | ||
params.processQueryLimit = api.BigNumber(processQueryLimit).toNumber(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these can be integer type, and can be checked with Number.isInteger() (see other contracts)
|
||
if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') | ||
&& api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') | ||
&& api.assert(typeof instanceId === 'string' && api.BigNumber(instanceId).isInteger(), 'invalid instanceId') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, can use integer check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to cast things toNumber everywhere else then
for (let i = 0; i < roles.length; i += 1) { | ||
const role = roles[i]; | ||
if (!api.assert(Object.keys(role).length === 5 | ||
&& typeof role.name === 'string' && role.name.length < 50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
split these into their own error messages?
await api.executeSmartContract('tokens', 'transfer', { | ||
to: 'null', symbol: inst.candidateFee.symbol, quantity: inst.candidateFee.amount, | ||
}); | ||
} else if (inst.candidateFee.method === 'issuer') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we can consolidate these by having them specify a 'feeAccount', and that will cover both of these cases and allow a different recipient as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, more flexibility is always good and it feels slightly less clunky from a usage perspective. Also, as per my other comments fees should be handled & verified before doing the insert.
const roleTickTime = api.BigNumber(blockDate.getTime()) | ||
.minus(role.tickHours * 3600 * 1000).toNumber(); | ||
|
||
if (role.lastTickTime <= roleTickTime) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be part of the query as well? currently it checks <= instTickTime but we can have it be <= min(two tick times)
.toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); | ||
|
||
if (candidate.active === true) { | ||
if (funded.length < role.mainSlots || api.BigNumber(backupWeight).lte(accWeight)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backup mechanism seems a bit weird, or works only when numBackup = 1. If there are more than 1 backups requested, and it generates a single number, it'll just pick two backups together without re-rolling the die? I think this might be similar to what witness contract does (Which is also wrong)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense since it was copied from witness contract. Going to rework.
|
||
await fixture.sendBlock(block); | ||
|
||
// let res = await fixture.database.getLatestBlockInfo(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove
done(); | ||
}); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this test deposits via dtf/distribution contracts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not currently, I was going to confirm in testnet
await tableAsserts.assertNoErrorInLastBlock(); | ||
} | ||
|
||
// distribution test suite |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
roles test suite
}; | ||
await api.db.update('candidates', candidate); | ||
|
||
const role = await api.db.findOne('roles', { _id: candidate.roleId }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if deltaToken is defined, then this is a redundant query (you've already looked up the role for deltaToken processing above). It would be more efficient to re-use the earlier query results in that case, and only execute the query again here if deltaToken is not defined.
if (api.sender !== api.owner | ||
&& api.sender !== 'null' | ||
&& api.BigNumber(instanceCreationFee).gt(0)) { | ||
await api.executeSmartContract('tokens', 'transfer', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the possibility of this transfer failing is remote, it would be safer to transfer the fees before creating the new instance, and verify that the transfer succeeded. See, for example, the technique used in the issue action of the nft contract.
if (api.sender !== api.owner | ||
&& api.sender !== 'null' | ||
&& api.BigNumber(instanceUpdateFee).gt(0)) { | ||
await api.executeSmartContract('tokens', 'transfer', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, should handle the fees and verify success before updating the instance.
if (api.sender !== api.owner | ||
&& api.sender !== 'null' | ||
&& api.BigNumber(roleCreationFee).gt(0)) { | ||
await api.executeSmartContract('tokens', 'transfer', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, should handle the fees and verify success first.
if (api.sender !== api.owner | ||
&& api.sender !== 'null' | ||
&& api.BigNumber(roleUpdateFee).gt(0)) { | ||
await api.executeSmartContract('tokens', 'transfer', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, should handle the fees and verify success first.
// deposit | ||
|
||
async function updateTokenBalances(role, token, quantity) { | ||
const upRole = role; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use this upRole
variable here instead of just directly using the passed in role
?
} | ||
}; | ||
|
||
actions.receiveDistTokens = async (payload) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code for this function is almost exactly the same as for receiveDtfTokens. Perhaps you could introduce a helper function used by both of these, to avoid so much code duplication?
await assertWeightConsistency(3, 'PRO'); | ||
await assertWeightConsistency(4, 'PRO'); | ||
|
||
assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this test case, how are the ticks set up to generate these virtual transactions? Maybe I missed it, but I didn't see any code to configure the per-block ticking behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall logical structure & organization looks fine, but the devil is in the details as they say. Please see my comments at specific code lines (and respond to all of eonwarped's comments).
Also, I'd like you to add some clear release instructions to the PR in regards to how to configure / setup the per-block ticking behavior for virtual transactions. I think I remember we moved all that to config so no code changes would be needed to the main node software, but I don't recall all the details and don't want to be trying to figure it out when I'm in the middle of releasing this.
Description
This contract provides a stake-based remuneration capability for a DAO to establish community roles.
Community members may then, in a decentralized fashion, vie for positions based on stake-weighted approval gained from the voting token stakers.
Candidates are ranked by approval weight in the configured voting token. A number of regular (main) ranked slots receive recurring remuneration, and a number of alternate (backup) ranked slots receive remuneration using a random rotation of remaining eligible candidates. Token balances are fully distributed on each tick, so funds must be provided using manual deposit, or other smart contract (i.e DTF, Distribution).
More in docs.
PR Details