From a487d69d28ef11916504c07bfaa25075928f7a36 Mon Sep 17 00:00:00 2001 From: eonwarped Date: Wed, 3 Sep 2025 21:18:27 +0000 Subject: [PATCH] fix subscribtion errors still adding to allowlist --- contracts/resourcemanager.js | 6 +- contracts/resourcemanager_minify.js | 2 +- test/resourcemanager.js | 87 ++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/contracts/resourcemanager.js b/contracts/resourcemanager.js index 3683b37d..e20b93c2 100644 --- a/contracts/resourcemanager.js +++ b/contracts/resourcemanager.js @@ -237,16 +237,16 @@ actions.subscribe = async (payload) => { let accountControl = await api.db.findOne(table, { account: sender }); const create = !accountControl; - api.assert(!accountControl || !accountControl.isDenied, 'cannot be purchased as long as you are throttled.'); + if (!api.assert(!accountControl || !accountControl.isDenied, 'cannot be purchased as long as you are throttled.')) return; const stillAllowed = isStillAllowed(accountControl); - api.assert(!stillAllowed, 'can only be purchased once a month.'); + if (!api.assert(!stillAllowed, 'can only be purchased once a month.')) return; const feeTransfer = await api.executeSmartContract('tokens', 'transfer', { to: 'null', symbol: burnParams.burnSymbol, quantity: burnParams.allowlistBurnFee, isSignedWithActiveKey: true, }); - api.assert(transferIsSuccessful(feeTransfer, 'transfer', api.sender, 'null', burnParams.burnSymbol, burnParams.allowlistBurnFee), 'not enough tokens for allowList fee'); + if (!api.assert(transferIsSuccessful(feeTransfer, 'transfer', api.sender, 'null', burnParams.burnSymbol, burnParams.allowlistBurnFee), 'not enough tokens for allowList fee')) return; const date = new Date(`${api.hiveBlockTimestamp}.000Z`); date.setDate(date.getDate() + 30); diff --git a/contracts/resourcemanager_minify.js b/contracts/resourcemanager_minify.js index 115e01fa..d4caee24 100644 --- a/contracts/resourcemanager_minify.js +++ b/contracts/resourcemanager_minify.js @@ -1 +1 @@ -actions.createSSC=async()=>{if(!1===await api.db.tableExists("params")){await api.db.createTable("params");const params={numberOfFreeTx:1,denyMaxTx:1,multiTransactionFee:"0.001",burnSymbol:"BEED",allowlistBurnFee:"10"};await api.db.insert("params",params)}!1===await api.db.tableExists("accountControls")&&await api.db.createTable("accountControls",[],{primaryKey:["account"]});!1===await api.db.tableExists("moderators")&&await api.db.createTable("moderators",[],{primaryKey:["account"]})},actions.updateParams=async payload=>{if(!api.assert(api.sender===api.owner,"not authorized"))return;const{numberOfFreeTx:numberOfFreeTx,multiTransactionFee:multiTransactionFee,burnSymbol:burnSymbol,denyMaxTx:denyMaxTx,allowlistBurnFee:allowlistBurnFee}=payload,params=await api.db.findOne("params",{});if(numberOfFreeTx){if(!api.assert(Number.isInteger(numberOfFreeTx)&&numberOfFreeTx>=1,"invalid numberOfFreeTx"))return;params.numberOfFreeTx=numberOfFreeTx}if(multiTransactionFee){const feeBN=api.BigNumber(multiTransactionFee);if(!api.assert("string"==typeof multiTransactionFee&&!feeBN.isNaN()&&feeBN.gte(0)&&feeBN.isFinite(),"invalid multiTransactionFee"))return;params.multiTransactionFee=multiTransactionFee}if(denyMaxTx){if(!api.assert(Number.isInteger(denyMaxTx)&&denyMaxTx>=0,"invalid denyMaxTx"))return;params.denyMaxTx=denyMaxTx}if(burnSymbol){if(!api.assert("string"==typeof burnSymbol,"invalid burnSymbol"))return;const token=await api.db.findOneInTable("tokens","tokens",{symbol:burnSymbol});if(!api.assert(token,"burnSymbol not available"))return;params.burnSymbol=burnSymbol}if(allowlistBurnFee){const allowCostsBN=api.BigNumber(allowlistBurnFee);if(!api.assert("string"==typeof allowlistBurnFee&&!allowCostsBN.isNaN()&&allowCostsBN.gte(0)&&allowCostsBN.isFinite(),"invalid allowlistBurnFee"))return;params.allowlistBurnFee=allowlistBurnFee}await api.db.update("params",params)},actions.updateAccount=async payload=>{const{sender:sender}=api,moderator=await api.db.findOne("moderators",{account:sender});if(!api.assert(sender===api.owner||moderator,"not authorized"))return;const table="accountControls",{account:account,isDenied:isDenied,isAllowed:isAllowed}=payload,timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let accountControl=await api.db.findOne(table,{account:account});const create=!accountControl;if(accountControl||(accountControl={account:account}),api.assert(null!=accountControl.account&&"undefined"!==accountControl.account,"invalid account")){if(null!=isDenied){if(!api.assert("boolean"==typeof isDenied,"invalid isDenied"))return;accountControl.isDenied=isDenied}if(null!=isAllowed){if(!api.assert("boolean"==typeof isAllowed,"invalid isAllowed"))return;accountControl.isAllowed=isAllowed}accountControl.lastAction=timestamp,create?await api.db.insert(table,accountControl):await api.db.update(table,accountControl),api.emit("updateAccount",{from:api.sender,updatedAccount:account,isDenied:isDenied,isAllowed:isAllowed})}},actions.updateModerator=async payload=>{const{sender:sender}=api;if(!api.assert(sender===api.owner,"not authorized"))return;const table="moderators",{account:account,action:action}=payload,moderator=await api.db.findOne(table,{account:account});let updated=!1;"add"!==action||moderator||(await api.db.insert(table,{account:account}),updated=!0),"remove"===action&&moderator&&(await api.db.remove(table,moderator),updated=!0),updated&&api.emit("updateModerator",{from:api.sender,account:account,action:action})};const transferIsSuccessful=(result,action,from,to,symbol,quantity)=>void 0===result.errors&&result.events&&void 0!==result.events.find((el=>"tokens"===el.contract&&el.event===action&&el.data.from===from&&el.data.to===to&&api.BigNumber(el.data.quantity).eq(quantity)&&el.data.symbol===symbol)),isStillAllowed=accountControls=>{if(!accountControls||!accountControls.isAllowed)return!1;if(!accountControls.allowedUntil)return!1;return new Date(`${api.hiveBlockTimestamp}.000Z`).getTime(){const{sender:sender}=api;if("null"===sender||null==sender)return;const burnParams=await api.db.findOne("params",{}),accountControls=await api.db.findOne("accountControls",{account:sender});if(accountControls&&accountControls.isDenied){const nowTimestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime(),diffHours=(nowTimestamp-accountControls.lastAction)/36e5;if(accountControls.actionCount=(accountControls.actionCount||0)+1,accountControls.lastAction=nowTimestamp,diffHours>=24&&(accountControls.actionCount=1),!api.assert(diffHours>=24||accountControls.actionCount<=burnParams.denyMaxTx,"max transaction limit per day reached."))return;await api.db.update("accountControls",accountControls)}if(payload.userActionCount<=burnParams.numberOfFreeTx)return;if(accountControls&&accountControls.isAllowed){if(isStillAllowed(accountControls))return;accountControls.isAllowed=!1,api.emit("allowListSubscriptionExpired",{from:api.sender}),await api.db.update("accountControls",accountControls)}if("market"!==payload.contract)return void("marketpools"!==payload.contract&&api.assert(payload.userActionCount<=20,"max transaction limit per block reached."));const feeTransfer=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:burnParams.burnSymbol,quantity:burnParams.multiTransactionFee,isSignedWithActiveKey:!0});api.assert(transferIsSuccessful(feeTransfer,"transfer",api.sender,"null",burnParams.burnSymbol,burnParams.multiTransactionFee),"not enough tokens for multiTransaction fee"),api.emit("burnFee",{from:api.sender,to:"null",symbol:burnParams.burnSymbol,fee:burnParams.multiTransactionFee})},actions.subscribe=async payload=>{const{sender:sender}=api;if("null"===sender||null==sender)return;const table="accountControls",burnParams=await api.db.findOne("params",{});let accountControl=await api.db.findOne(table,{account:sender});const create=!accountControl;api.assert(!accountControl||!accountControl.isDenied,"cannot be purchased as long as you are throttled.");const stillAllowed=isStillAllowed(accountControl);api.assert(!stillAllowed,"can only be purchased once a month.");const feeTransfer=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:burnParams.burnSymbol,quantity:burnParams.allowlistBurnFee,isSignedWithActiveKey:!0});api.assert(transferIsSuccessful(feeTransfer,"transfer",api.sender,"null",burnParams.burnSymbol,burnParams.allowlistBurnFee),"not enough tokens for allowList fee");const date=new Date(`${api.hiveBlockTimestamp}.000Z`);date.setDate(date.getDate()+30),accountControl||(accountControl={account:sender,isDenied:!1,isAllowed:!0}),accountControl.allowedUntil=date.getTime(),accountControl.isAllowed=!0,create?await api.db.insert(table,accountControl):await api.db.update(table,accountControl),api.emit("subscribe",{from:api.sender,to:"null",symbol:burnParams.burnSymbol,fee:burnParams.allowlistBurnFee,subscribedUntil:accountControl.allowedUntil})}; \ No newline at end of file +actions.createSSC=async()=>{if(!1===await api.db.tableExists("params")){await api.db.createTable("params");const params={numberOfFreeTx:1,denyMaxTx:1,multiTransactionFee:"0.001",burnSymbol:"BEED",allowlistBurnFee:"10"};await api.db.insert("params",params)}!1===await api.db.tableExists("accountControls")&&await api.db.createTable("accountControls",[],{primaryKey:["account"]});!1===await api.db.tableExists("moderators")&&await api.db.createTable("moderators",[],{primaryKey:["account"]})},actions.updateParams=async payload=>{if(!api.assert(api.sender===api.owner,"not authorized"))return;const{numberOfFreeTx:numberOfFreeTx,multiTransactionFee:multiTransactionFee,burnSymbol:burnSymbol,denyMaxTx:denyMaxTx,allowlistBurnFee:allowlistBurnFee}=payload,params=await api.db.findOne("params",{});if(numberOfFreeTx){if(!api.assert(Number.isInteger(numberOfFreeTx)&&numberOfFreeTx>=1,"invalid numberOfFreeTx"))return;params.numberOfFreeTx=numberOfFreeTx}if(multiTransactionFee){const feeBN=api.BigNumber(multiTransactionFee);if(!api.assert("string"==typeof multiTransactionFee&&!feeBN.isNaN()&&feeBN.gte(0)&&feeBN.isFinite(),"invalid multiTransactionFee"))return;params.multiTransactionFee=multiTransactionFee}if(denyMaxTx){if(!api.assert(Number.isInteger(denyMaxTx)&&denyMaxTx>=0,"invalid denyMaxTx"))return;params.denyMaxTx=denyMaxTx}if(burnSymbol){if(!api.assert("string"==typeof burnSymbol,"invalid burnSymbol"))return;const token=await api.db.findOneInTable("tokens","tokens",{symbol:burnSymbol});if(!api.assert(token,"burnSymbol not available"))return;params.burnSymbol=burnSymbol}if(allowlistBurnFee){const allowCostsBN=api.BigNumber(allowlistBurnFee);if(!api.assert("string"==typeof allowlistBurnFee&&!allowCostsBN.isNaN()&&allowCostsBN.gte(0)&&allowCostsBN.isFinite(),"invalid allowlistBurnFee"))return;params.allowlistBurnFee=allowlistBurnFee}await api.db.update("params",params)},actions.updateAccount=async payload=>{const{sender:sender}=api,moderator=await api.db.findOne("moderators",{account:sender});if(!api.assert(sender===api.owner||moderator,"not authorized"))return;const table="accountControls",{account:account,isDenied:isDenied,isAllowed:isAllowed}=payload,timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let accountControl=await api.db.findOne(table,{account:account});const create=!accountControl;if(accountControl||(accountControl={account:account}),api.assert(null!=accountControl.account&&"undefined"!==accountControl.account,"invalid account")){if(null!=isDenied){if(!api.assert("boolean"==typeof isDenied,"invalid isDenied"))return;accountControl.isDenied=isDenied}if(null!=isAllowed){if(!api.assert("boolean"==typeof isAllowed,"invalid isAllowed"))return;accountControl.isAllowed=isAllowed}accountControl.lastAction=timestamp,create?await api.db.insert(table,accountControl):await api.db.update(table,accountControl),api.emit("updateAccount",{from:api.sender,updatedAccount:account,isDenied:isDenied,isAllowed:isAllowed})}},actions.updateModerator=async payload=>{const{sender:sender}=api;if(!api.assert(sender===api.owner,"not authorized"))return;const table="moderators",{account:account,action:action}=payload,moderator=await api.db.findOne(table,{account:account});let updated=!1;"add"!==action||moderator||(await api.db.insert(table,{account:account}),updated=!0),"remove"===action&&moderator&&(await api.db.remove(table,moderator),updated=!0),updated&&api.emit("updateModerator",{from:api.sender,account:account,action:action})};const transferIsSuccessful=(result,action,from,to,symbol,quantity)=>void 0===result.errors&&result.events&&void 0!==result.events.find((el=>"tokens"===el.contract&&el.event===action&&el.data.from===from&&el.data.to===to&&api.BigNumber(el.data.quantity).eq(quantity)&&el.data.symbol===symbol)),isStillAllowed=accountControls=>{if(!accountControls||!accountControls.isAllowed)return!1;if(!accountControls.allowedUntil)return!1;return new Date(`${api.hiveBlockTimestamp}.000Z`).getTime(){const{sender:sender}=api;if("null"===sender||null==sender)return;const burnParams=await api.db.findOne("params",{}),accountControls=await api.db.findOne("accountControls",{account:sender});if(accountControls&&accountControls.isDenied){const nowTimestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime(),diffHours=(nowTimestamp-accountControls.lastAction)/36e5;if(accountControls.actionCount=(accountControls.actionCount||0)+1,accountControls.lastAction=nowTimestamp,diffHours>=24&&(accountControls.actionCount=1),!api.assert(diffHours>=24||accountControls.actionCount<=burnParams.denyMaxTx,"max transaction limit per day reached."))return;await api.db.update("accountControls",accountControls)}if(payload.userActionCount<=burnParams.numberOfFreeTx)return;if(accountControls&&accountControls.isAllowed){if(isStillAllowed(accountControls))return;accountControls.isAllowed=!1,api.emit("allowListSubscriptionExpired",{from:api.sender}),await api.db.update("accountControls",accountControls)}if("market"!==payload.contract)return void("marketpools"!==payload.contract&&api.assert(payload.userActionCount<=20,"max transaction limit per block reached."));const feeTransfer=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:burnParams.burnSymbol,quantity:burnParams.multiTransactionFee,isSignedWithActiveKey:!0});api.assert(transferIsSuccessful(feeTransfer,"transfer",api.sender,"null",burnParams.burnSymbol,burnParams.multiTransactionFee),"not enough tokens for multiTransaction fee"),api.emit("burnFee",{from:api.sender,to:"null",symbol:burnParams.burnSymbol,fee:burnParams.multiTransactionFee})},actions.subscribe=async payload=>{const{sender:sender}=api;if("null"===sender||null==sender)return;const table="accountControls",burnParams=await api.db.findOne("params",{});let accountControl=await api.db.findOne(table,{account:sender});const create=!accountControl;if(!api.assert(!accountControl||!accountControl.isDenied,"cannot be purchased as long as you are throttled."))return;const stillAllowed=isStillAllowed(accountControl);if(!api.assert(!stillAllowed,"can only be purchased once a month."))return;const feeTransfer=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:burnParams.burnSymbol,quantity:burnParams.allowlistBurnFee,isSignedWithActiveKey:!0});if(!api.assert(transferIsSuccessful(feeTransfer,"transfer",api.sender,"null",burnParams.burnSymbol,burnParams.allowlistBurnFee),"not enough tokens for allowList fee"))return;const date=new Date(`${api.hiveBlockTimestamp}.000Z`);date.setDate(date.getDate()+30),accountControl||(accountControl={account:sender,isDenied:!1,isAllowed:!0}),accountControl.allowedUntil=date.getTime(),accountControl.isAllowed=!0,create?await api.db.insert(table,accountControl):await api.db.update(table,accountControl),api.emit("subscribe",{from:api.sender,to:"null",symbol:burnParams.burnSymbol,fee:burnParams.allowlistBurnFee,subscribedUntil:accountControl.allowedUntil})}; \ No newline at end of file diff --git a/test/resourcemanager.js b/test/resourcemanager.js index b8f36207..001b1f61 100644 --- a/test/resourcemanager.js +++ b/test/resourcemanager.js @@ -755,10 +755,93 @@ describe('resourcemanager', function () { await fixture.sendBlock(block); const res = await fixture.database.getLatestBlockInfo(); - + const log0 = JSON.parse(res.transactions[0].logs); assert.ok(log0.errors || log0.errors.length >= 1, 'Transaction should fail'); assert.equal(log0.errors[0], 'can only be purchased once a month.', 'Transaction should fail with correct error'); + + let dbRes = await fixture.database.findOne({ + contract: 'resourcemanager', + table: 'accountControls', + query: { + account: 'tate' + } + }); + let newValidUntil = new Date(`${res.timestamp}.000Z`); + newValidUntil.setDate(newValidUntil.getDate() + 30); + const newValidUntilMs = newValidUntil.getTime(); + assert.ok(dbRes.allowedUntil < newValidUntilMs, 'timestamp should not be refreshed'); + assert.ok(dbRes.isAllowed === true); + }); + + it('allowlist subscription not enough for fee', async () => { + await fixture.setUp(); + + await initializeResourceManager(); + + let refBlockNumber = resourceManagerForkBlock; + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'tate2', 'resourcemanager', 'subscribe', '{ "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2025-05-12T16:30:03', + transactions, + }; + await fixture.sendBlock(block); + + const res = await fixture.database.getLatestBlockInfo(); + + const log0 = JSON.parse(res.transactions[0].logs); + assert.ok(log0.errors || log0.errors.length >= 1, 'Transaction should fail'); + assert.equal(log0.errors[1], 'not enough tokens for allowList fee', 'Transaction should fail with correct error'); + + let dbRes = await fixture.database.findOne({ + contract: 'resourcemanager', + table: 'accountControls', + query: { + account: 'tate2' + } + }); + assert.ok(!dbRes, 'should not have account control'); + }); + + it('allowlist subscription in denylist', async () => { + await fixture.setUp(); + + await initializeResourceManager(); + + let refBlockNumber = resourceManagerForkBlock; + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'resourcemanager', 'updateAccount', '{"account": "tate2", "isDenied": true}' )); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'tate2', 'resourcemanager', 'subscribe', '{ "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2025-05-12T16:30:03', + transactions, + }; + await fixture.sendBlock(block); + + const res = await fixture.database.getLatestBlockInfo(); + + const log1 = JSON.parse(res.transactions[1].logs); + assert.ok(log1.errors || log1.errors.length >= 1, 'Transaction should fail'); + assert.equal(log1.errors[0], 'cannot be purchased as long as you are throttled.', 'Transaction should fail with correct error'); + + let dbRes = await fixture.database.findOne({ + contract: 'resourcemanager', + table: 'accountControls', + query: { + account: 'tate2' + } + }); + assert.ok(dbRes.isDenied, 'should be denied'); + assert.ok(!dbRes.isAllowed, 'should not be allowed'); }); it('allowlist expiration', async () => { @@ -794,7 +877,7 @@ describe('resourcemanager', function () { await fixture.sendBlock(block); const res = await fixture.database.getLatestBlockInfo(); - + const log1 = JSON.parse(res.transactions[1].logs); assert.ok(log1.events && log1.events.length >= 1, 'Transaction should have events'); assert.ok(log1.events[0].contract === 'resourcemanager' && log1.events[0].event === 'allowListSubscriptionExpired', 'AllowList subscription should have expired');