From f70ab1de8cc0b0115746b998f0e537e2b1bd8dd7 Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 15 Feb 2023 17:44:05 +0545 Subject: [PATCH 01/56] creating dex pool script --- coralX-scenarios.js | 5 ++ scripts/units/create_pool_dex.js | 141 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 scripts/units/create_pool_dex.js diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 5a9f832..211a6fa 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -36,6 +36,11 @@ module.exports = { ['compile'], ['execute', '--path', 'scripts/units/setup_council_stakes.js', '--network', 'apothem'], ], + + createDexPoolApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], + ], migrateAndConfigureForTests: [ ['compile'], diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js new file mode 100644 index 0000000..5cf328a --- /dev/null +++ b/scripts/units/create_pool_dex.js @@ -0,0 +1,141 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); + +const Token_A_Address = "" +const Token_B_Address = "" +const Amount_A_Desired = web3.utils.toWei('100', 'ether') +const Amount_B_Desired = web3.utils.toWei('100', 'ether') +const Amount_A_Minimum = web3.utils.toWei('100', 'ether') +const Amount_B_Minimum = web3.utils.toWei('100', 'ether') +const DEX_ROUTER_ADDRESS = "" +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeAddLiqudityFunction = ( + _tokenA, + _tokenB, + _amountADesired, + _amountBDesired, + _amountAMin, + _amountBMin, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'addLiquidity', + type: 'function', + inputs: [{ + type: 'address', + name: 'tokenA' + }, + { + type: 'address', + name: 'tokenB' + }, + { + type: 'uint', + name: 'amountADesired' + }, + { + type: 'uint', + name: 'amountBDesired' + }, + { + type: 'uint', + name: 'amountAMin' + }, + { + type: 'uint', + name: 'amountBMin' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint', + name: 'deadline' + }] + }, [_tokenA, + _tokenB, + _amountADesired, + _amountBDesired, + _amountAMin, + _amountBMin, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const deadline = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP//NOTE: Please change it + let resultApprove_A = await multiSigWallet.submitTransaction( + Token_A_Address, + EMPTY_BYTES, + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), + 0, + {gas: 8000000} + ) + + let txIndexApprove_A = eventsHelper.getIndexedEventArgs(resultApprove_A, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexApprove_A, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexApprove_A, {gas: 8000000}); + + let resultApprove_B = await multiSigWallet.submitTransaction( + Token_B_Address, + EMPTY_BYTES, + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), + 0, + {gas: 8000000} + ) + + let txIndexApprove_B = eventsHelper.getIndexedEventArgs(resultApprove_B, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexApprove_B, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexApprove_B, {gas: 8000000}); + + let resultAddLiquidity = await multiSigWallet.submitTransaction( + DEX_ROUTER_ADDRESS, + EMPTY_BYTES, + _encodeAddLiqudityFunction( + Token_A_Address, + Token_B_Address, + Amount_A_Desired, + Amount_B_Desired, + Amount_A_Minimum, + Amount_B_Minimum, + multiSigWallet.address, + deadline + ), + 0, + {gas: 8000000} + ) + + let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); +} + + From a69f4a79b06b06421772b145707ffeaac8ab1dac Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 09:49:30 +0545 Subject: [PATCH 02/56] creating pool dex --- coralX-scenarios.js | 9 ++- scripts/units/create_pool_dex.js | 24 +++--- scripts/units/create_pool_dex_xdc.js | 117 +++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 scripts/units/create_pool_dex_xdc.js diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 211a6fa..ed12adc 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -37,6 +37,11 @@ module.exports = { ['execute', '--path', 'scripts/units/setup_council_stakes.js', '--network', 'apothem'], ], + createDexXDCPoolApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create_pool_dex_xdc.js', '--network', 'apothem'], + ], + createDexPoolApothem: [ ['compile'], ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], @@ -48,7 +53,5 @@ module.exports = { ['execute', '--path', 'scripts/migrations/test'], ['execute', '--path', 'scripts/migrations/upgrades'], ], - createStablecoinPool: [ - ['execute', '--path', 'scripts/stablecoin-integration/create-pool-through-governance.js'] - ], + } diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 5cf328a..91bedc7 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -8,13 +8,13 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const Token_A_Address = "" -const Token_B_Address = "" -const Amount_A_Desired = web3.utils.toWei('100', 'ether') +const Token_A_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC address +const Token_B_Address = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //US+ address +const Amount_A_Desired = web3.utils.toWei('3', 'ether') const Amount_B_Desired = web3.utils.toWei('100', 'ether') -const Amount_A_Minimum = web3.utils.toWei('100', 'ether') -const Amount_B_Minimum = web3.utils.toWei('100', 'ether') -const DEX_ROUTER_ADDRESS = "" +const Amount_A_Minimum = web3.utils.toWei('1', 'ether') +const Amount_B_Minimum = web3.utils.toWei('1', 'ether') +const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -53,19 +53,19 @@ const _encodeAddLiqudityFunction = ( name: 'tokenB' }, { - type: 'uint', + type: 'uint256', name: 'amountADesired' }, { - type: 'uint', + type: 'uint256', name: 'amountBDesired' }, { - type: 'uint', + type: 'uint256', name: 'amountAMin' }, { - type: 'uint', + type: 'uint256', name: 'amountBMin' }, { @@ -73,7 +73,7 @@ const _encodeAddLiqudityFunction = ( name: 'to' }, { - type: 'uint', + type: 'uint256', name: 'deadline' }] }, [_tokenA, @@ -115,7 +115,7 @@ module.exports = async function(deployer) { let txIndexApprove_B = eventsHelper.getIndexedEventArgs(resultApprove_B, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApprove_B, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApprove_B, {gas: 8000000}); - + let resultAddLiquidity = await multiSigWallet.submitTransaction( DEX_ROUTER_ADDRESS, EMPTY_BYTES, diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js new file mode 100644 index 0000000..b155741 --- /dev/null +++ b/scripts/units/create_pool_dex_xdc.js @@ -0,0 +1,117 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); +const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); + +const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address +const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('5', 'ether') +const AMOUNT_TOKEN_MIN = web3.utils.toWei('3', 'ether') +const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') +const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router + + +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + + +const _encodeAddLiqudityFunction = ( + _token, + _amountTokenDesired, + _amountTokenMin, + _amountETHMin, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'addLiquidityETH', + type: 'function', + inputs: [{ + type: 'address', + name: 'token' + }, + { + type: 'uint', + name: 'amountTokenDesired' + }, + { + type: 'uint', + name: 'amountTokenMin' + }, + { + type: 'uint', + name: 'amountETHMin' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint', + name: 'deadline' + }] + }, [_token, + _amountTokenDesired, + _amountTokenMin, + _amountETHMin, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const deadline = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP//NOTE: Please change it + + + let resultApprove = await multiSigWallet.submitTransaction( + TOKEN_ADDRESS, + EMPTY_BYTES, + _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_TOKEN_DESIRED), + 0, + {gas: 8000000} + ) + + let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexApprove, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); + let resultAddLiquidity = await multiSigWallet.submitTransaction( + DEX_ROUTER_ADDRESS, + AMOUNT_TOKEN_DESIRED, + _encodeAddLiqudityFunction( + TOKEN_ADDRESS, + AMOUNT_TOKEN_DESIRED, + AMOUNT_TOKEN_MIN, + AMOUNT_ETH_MIN, + multiSigWallet.address, + deadline + ), + 0, + {gas: 8000000} + ) + let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); +} + + From 3dec38135789d44c5ad5a14cc536146ecc44ed0e Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 10:58:45 +0545 Subject: [PATCH 03/56] create dex pool --- scripts/units/create_pool_dex.js | 12 +++++++----- scripts/units/create_pool_dex_xdc.js | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 91bedc7..83108f9 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -8,12 +8,14 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const Token_A_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC address -const Token_B_Address = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //US+ address -const Amount_A_Desired = web3.utils.toWei('3', 'ether') +const Token_A_Address = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //Fthm +const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //Fthm +const Amount_A_Desired = web3.utils.toWei('100', 'ether') const Amount_B_Desired = web3.utils.toWei('100', 'ether') const Amount_A_Minimum = web3.utils.toWei('1', 'ether') const Amount_B_Minimum = web3.utils.toWei('1', 'ether') + +const Token_TO_APPROVE = web3.utils.toWei('10000', 'ether') const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -95,7 +97,7 @@ module.exports = async function(deployer) { let resultApprove_A = await multiSigWallet.submitTransaction( Token_A_Address, EMPTY_BYTES, - _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Token_TO_APPROVE), 0, {gas: 8000000} ) @@ -107,7 +109,7 @@ module.exports = async function(deployer) { let resultApprove_B = await multiSigWallet.submitTransaction( Token_B_Address, EMPTY_BYTES, - _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Token_TO_APPROVE), 0, {gas: 8000000} ) diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index b155741..7224667 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -111,7 +111,7 @@ module.exports = async function(deployer) { ) let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); } From 4cfda9ff1edb6de6029b157987fd39e27f54ed61 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 11:02:37 +0545 Subject: [PATCH 04/56] changed address --- scripts/units/create_pool_dex.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 83108f9..d5cebf5 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -8,14 +8,16 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const Token_A_Address = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //Fthm -const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //Fthm -const Amount_A_Desired = web3.utils.toWei('100', 'ether') -const Amount_B_Desired = web3.utils.toWei('100', 'ether') + +const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ +const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC +// SET AS Necessary +const Amount_A_Desired = web3.utils.toWei('5', 'ether') +const Amount_B_Desired = web3.utils.toWei('5', 'ether') const Amount_A_Minimum = web3.utils.toWei('1', 'ether') const Amount_B_Minimum = web3.utils.toWei('1', 'ether') -const Token_TO_APPROVE = web3.utils.toWei('10000', 'ether') +const Token_TO_APPROVE = web3.utils.toWei('1000000000000000000', 'ether') const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -93,6 +95,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + //Will need to change it once it expires const deadline = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP//NOTE: Please change it let resultApprove_A = await multiSigWallet.submitTransaction( Token_A_Address, From d912b6e54dedd83aff7de52d213ec3c4813c77be Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 11:09:40 +0545 Subject: [PATCH 05/56] changed to required addresses --- coralX-scenarios.js | 5 ----- scripts/units/create_pool_dex.js | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/coralX-scenarios.js b/coralX-scenarios.js index ed12adc..82bb5bf 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -37,11 +37,6 @@ module.exports = { ['execute', '--path', 'scripts/units/setup_council_stakes.js', '--network', 'apothem'], ], - createDexXDCPoolApothem: [ - ['compile'], - ['execute', '--path', 'scripts/units/create_pool_dex_xdc.js', '--network', 'apothem'], - ], - createDexPoolApothem: [ ['compile'], ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index d5cebf5..733dc90 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -12,13 +12,13 @@ const addresses = JSON.parse(rawdata); const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC // SET AS Necessary -const Amount_A_Desired = web3.utils.toWei('5', 'ether') -const Amount_B_Desired = web3.utils.toWei('5', 'ether') -const Amount_A_Minimum = web3.utils.toWei('1', 'ether') -const Amount_B_Minimum = web3.utils.toWei('1', 'ether') +const Amount_A_Desired = web3.utils.toWei('250000', 'ether') +const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') +const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') +const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') const Token_TO_APPROVE = web3.utils.toWei('1000000000000000000', 'ether') -const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router +const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', From d94151ba084341671bf30142247a381871b68f12 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 11:15:50 +0545 Subject: [PATCH 06/56] setting lower token balance value --- scripts/units/create_pool_dex.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 733dc90..f9e19e7 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -12,10 +12,15 @@ const addresses = JSON.parse(rawdata); const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC // SET AS Necessary -const Amount_A_Desired = web3.utils.toWei('250000', 'ether') -const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') -const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') -const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') +const Amount_A_Desired = web3.utils.toWei('2', 'ether') +const Amount_B_Desired = web3.utils.toWei('38', 'ether') +const Amount_A_Minimum = web3.utils.toWei('1', 'ether') +const Amount_B_Minimum = web3.utils.toWei('1', 'ether') + +// const Amount_A_Desired = web3.utils.toWei('250000', 'ether') +// const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') +// const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') +// const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') const Token_TO_APPROVE = web3.utils.toWei('1000000000000000000', 'ether') const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER From 9e12c96211907c422c92113a3f4c0ef882246d91 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 14:00:17 +0545 Subject: [PATCH 07/56] creating liqudiity with ETH --- coralX-scenarios.js | 5 +++++ scripts/units/create_pool_dex.js | 2 +- scripts/units/create_pool_dex_xdc.js | 13 ++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 82bb5bf..ed12adc 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -37,6 +37,11 @@ module.exports = { ['execute', '--path', 'scripts/units/setup_council_stakes.js', '--network', 'apothem'], ], + createDexXDCPoolApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create_pool_dex_xdc.js', '--network', 'apothem'], + ], + createDexPoolApothem: [ ['compile'], ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index f9e19e7..ad841ae 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -21,7 +21,7 @@ const Amount_B_Minimum = web3.utils.toWei('1', 'ether') // const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') // const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') // const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') - +//What should const Token_TO_APPROVE = web3.utils.toWei('1000000000000000000', 'ether') const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER const _encodeApproveFunction = (_account, _amount) => { diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 7224667..0212f53 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -14,8 +14,7 @@ const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('5', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('3', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router - - +const TOKEN_ETH = web3.utils.toWei('10', 'ether') const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -49,15 +48,15 @@ const _encodeAddLiqudityFunction = ( name: 'token' }, { - type: 'uint', + type: 'uint256', name: 'amountTokenDesired' }, { - type: 'uint', + type: 'uint256', name: 'amountTokenMin' }, { - type: 'uint', + type: 'uint256', name: 'amountETHMin' }, { @@ -65,7 +64,7 @@ const _encodeAddLiqudityFunction = ( name: 'to' }, { - type: 'uint', + type: 'uint256', name: 'deadline' }] }, [_token, @@ -97,7 +96,7 @@ module.exports = async function(deployer) { await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); let resultAddLiquidity = await multiSigWallet.submitTransaction( DEX_ROUTER_ADDRESS, - AMOUNT_TOKEN_DESIRED, + TOKEN_ETH, _encodeAddLiqudityFunction( TOKEN_ADDRESS, AMOUNT_TOKEN_DESIRED, From ca0432cad15c0d6be819dcc58d75b5aa84df6176 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 14:12:54 +0545 Subject: [PATCH 08/56] changing approval --- scripts/units/create_pool_dex.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index ad841ae..b572dfe 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -22,7 +22,6 @@ const Amount_B_Minimum = web3.utils.toWei('1', 'ether') // const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') // const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') //What should -const Token_TO_APPROVE = web3.utils.toWei('1000000000000000000', 'ether') const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -105,7 +104,7 @@ module.exports = async function(deployer) { let resultApprove_A = await multiSigWallet.submitTransaction( Token_A_Address, EMPTY_BYTES, - _encodeApproveFunction(DEX_ROUTER_ADDRESS,Token_TO_APPROVE), + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), 0, {gas: 8000000} ) @@ -117,7 +116,7 @@ module.exports = async function(deployer) { let resultApprove_B = await multiSigWallet.submitTransaction( Token_B_Address, EMPTY_BYTES, - _encodeApproveFunction(DEX_ROUTER_ADDRESS,Token_TO_APPROVE), + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_B_Desired), 0, {gas: 8000000} ) From 0d27fab70828376a9eb2ff322f2b4b2e21444df3 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 16 Feb 2023 18:30:04 +0545 Subject: [PATCH 09/56] creating CDP with proxy wallet --- .../dao/test/stablecoin/IProxyRegistry.sol | 10 ++ coralX-scenarios.js | 9 ++ .../units/create_stablecoin_open_position.js | 112 ++++++++++++++++++ .../units/create_stablecoin_proxy_wallet.js | 52 ++++++++ 4 files changed, 183 insertions(+) create mode 100644 contracts/dao/test/stablecoin/IProxyRegistry.sol create mode 100644 scripts/units/create_stablecoin_open_position.js create mode 100644 scripts/units/create_stablecoin_proxy_wallet.js diff --git a/contracts/dao/test/stablecoin/IProxyRegistry.sol b/contracts/dao/test/stablecoin/IProxyRegistry.sol new file mode 100644 index 0000000..3971444 --- /dev/null +++ b/contracts/dao/test/stablecoin/IProxyRegistry.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity 0.8.16; + +interface IProxyRegistry { + function proxies(address) external view returns (address); + + function build(address) external returns (address); + + function isProxy(address) external view returns (bool); +} \ No newline at end of file diff --git a/coralX-scenarios.js b/coralX-scenarios.js index ed12adc..89cefa4 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -47,6 +47,15 @@ module.exports = { ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], ], + createProxyWalletApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create_stablecoin_proxy_wallet.js', '--network', 'apothem'], + ], + + openPositionApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create_stablecoin_open_position.js', '--network', 'apothem'], + ], migrateAndConfigureForTests: [ ['compile'], ['execute', '--path', 'scripts/migrations/deployment-test'], diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js new file mode 100644 index 0000000..167f412 --- /dev/null +++ b/scripts/units/create_stablecoin_open_position.js @@ -0,0 +1,112 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const rawDataStablecoin = fs.readFileSync('../../stablecoin-addresses.json'); +const addressesStableCoin = JSON.parse(rawDataStablecoin); +const XDC_COL = web3.utils.toWei('20','ether') +//xdcBe6f6500C3e45a78E17818570b99a7646F8b59F3 +const PROXY_WALLET = addressesStableCoin.proxyWallet +const positionMananger = "0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b" +const stabilityFeeCollector = "0x62889248B6C81D31D7acc450cc0334D0AA58A14A" +const xdcAdapter = "0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1" +const stablecoinAdapter = "0x07a2C89774a3F3c57980AD7A528Aea6F262d8939" +const collateralPoolId = '0x5844430000000000000000000000000000000000000000000000000000000000' +const stablecoinAmount = web3.utils.toWei('5') +const data = "0x00" + + +const _encodeOpenPositionCall = ( + _manager, + __stabilityFeeCollector, + _xdcAdapter, + _stablecoinAdapter, + _collateralPoolId, + _stablecoinAmount, + _data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'openLockXDCAndDraw', + type: 'function', + inputs: [{ + type: 'address', + name: '_manager' + },{ + type: 'address', + name: '_stabilityFeeCollector' + },{ + type: 'address', + name: '_xdcAdapter' + },{ + type: 'address', + name: '_stablecoinAdapter' + },{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_stablecoinAmount' + },{ + type: 'bytes', + name: '_data' + }] + }, [_manager, + __stabilityFeeCollector, + _xdcAdapter, + _stablecoinAdapter, + _collateralPoolId, + _stablecoinAmount, + _data]); + + return toRet; +} + +const _encodeExecute = (_data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'execute', + type: 'function', + inputs: [{ + type: 'bytes', + name: 'data' + }] + }, [_data]); + + return toRet; +} + + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + let openPostionCallEncoded = _encodeOpenPositionCall( + positionMananger, + stabilityFeeCollector, + xdcAdapter, + stablecoinAdapter, + collateralPoolId, + stablecoinAmount, + data + ) + + let resultExecute = await multiSigWallet.submitTransaction( + PROXY_WALLET, + XDC_COL, + _encodeExecute(openPostionCallEncoded), + 0, + {gas: 8000000} + ) + + let txExecute = eventsHelper.getIndexedEventArgs(resultExecute, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txExecute, {gas: 8000000}); + await multiSigWallet.executeTransaction(txExecute, {gas: 8000000}); + +} + + + +//{"proxyFactory":"0x2CF7cAe946D97232b4bA0a819B2d5c753fb6C2C5","simplePriceFeedUSDT":"0x981B8c630C46A6AD21686C58a57E2AFD7362d85e","fixedSpreadLiquidationStrategy":"0x754f0573a97cDEe2e47920a582C228e8b0A63a03","proxyWalletRegistry":"0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904","stabilityFeeCollector":"0x62889248B6C81D31D7acc450cc0334D0AA58A14A", +//"stablecoinAdapter":"0x07a2C89774a3F3c57980AD7A528Aea6F262d8939","showStopper":"0xce3d5feC112C34da459Decfec1bCeF3a6437A5Bc","priceOracle":"0xbCB7f95718d2eB899bB16B861c1e5416EAD36264","fathomStablecoin":"0x08B5860daD9947677F2a9d7DE563cBec9980E44c","positionManager":"0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b","systemDebtEngine":"0xFd28A89440e005562d0E8CD86C4574021C904FD9","liquidationEngine":"0x73EA9Acb76fC83c3BE5A75F5c6940EA623746Ed4","bookKeeper":"0xf5486A08a1528f97cc7da3eED9050DB2387ADf66","collateralPoolConfig":"0x279750eb5799010A295119f38A32A67723a9e10F","accessControlConfig":"0xaa96E05E5f4a024aaAF399A26F0D4341E420269e","flashMintModule":"0x39FAaef7F24B775fe0dDEB5fc62D819723398f65","stableSwapModule":"0x9F1c95900ca81b46F46cDce9C1762048cBc50CC6","flashMintArbitrager":"0xd7dA7B3F5fA605CDD77F09dB37d07fBc5c680fD0","bookKeeperFlashMintArbitrager":"0x00d42C140C2c448D0a7c1E1562D28D8733621c8E","dexPriceOracle":"0x23Bb26c4a59B28ad496bD3140AC7C95869F35830","proxyWalletFactory":"0x198bcA10F74caDAe58656B223e1D279686E1E672","fathomStablecoinProxyActions":"0x233380A9C66CCB6d2aC2BaEEAde5a509De2bb649","ankrCollateralAdapter":"0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1","delayFathomOraclePriceFeed":"0xd2b0E79136E471bf981559A6cE862f793C81701d"}{"51":{"WXDC":"0xE99500AB4A413164DA49Af83B9824749059b46ce","USD":"0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f","FTHM":"0x0000000000000000000000000000000000000000","DEXFactory":"0xe350508951929D3e19222824F41790621fb18A15", "xdcPool":"0xd458788DD7d2fDbB5238d9eeb0a49732BffF08b7","aXDCc":"0xe27990d8c950038C548E6f4BD0657aCE27495D48"} \ No newline at end of file diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js new file mode 100644 index 0000000..f158c7e --- /dev/null +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -0,0 +1,52 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.sol"); + +const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" +const _encodeBuildFunction = (_account) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'build', + type: 'function', + inputs: [{ + type: 'address', + name: 'owner' + }] + }, [_account]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const proxyRegistry = await IProxyRegistry.at(PROXY_WALLET_REGISTRY_ADDRESS) + let resultBuild= await multiSigWallet.submitTransaction( + PROXY_WALLET_REGISTRY_ADDRESS, + EMPTY_BYTES, + _encodeBuildFunction(multiSigWallet.address), + 0, + {gas: 8000000} + ) + + let txIndexBuild = eventsHelper.getIndexedEventArgs(resultBuild, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexBuild, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexBuild, {gas: 8000000}); + const proxyWalletAddress = await proxyRegistry.proxies(multiSigWallet.address) + let addressesStableCoin = { + proxyWallet: proxyWalletAddress + } + let data = JSON.stringify(addressesStableCoin); + + fs.writeFileSync('./stablecoin-addresses.json',data, function(err){ + if(err){ + console.log(err) + } + }) +} \ No newline at end of file From 5df4b793b4d17383a7751c7679e5d7d41d88e83a Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 17 Feb 2023 13:08:51 +0545 Subject: [PATCH 10/56] adding additional scripts --- coralX-scenarios.js | 5 ++ scripts/units/create-upgrade.js | 57 ++++++++++++++ scripts/units/stableswap-setup.js | 120 ++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 scripts/units/create-upgrade.js create mode 100644 scripts/units/stableswap-setup.js diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 89cefa4..4f48017 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -56,6 +56,11 @@ module.exports = { ['compile'], ['execute', '--path', 'scripts/units/create_stablecoin_open_position.js', '--network', 'apothem'], ], + + deployStakingUpgradeApothem: [ + ['compile'], + ['execute', '--path', 'scripts/upgrades/1_deploy_staking_upgrade.js', '--network', 'apothem'], + ], migrateAndConfigureForTests: [ ['compile'], ['execute', '--path', 'scripts/migrations/deployment-test'], diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js new file mode 100644 index 0000000..4684332 --- /dev/null +++ b/scripts/units/create-upgrade.js @@ -0,0 +1,57 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); + +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const PROXY_ADMIN = "" +const PROXY = "" +const IMPLEMENTATION_ADDRESS = "" +const _encodeUpgradeFunction = (_proxy, _impl) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'upgrade', + type: 'function', + inputs: [{ + type: 'address', + name: 'proxy' + },{ + type: 'address', + name: 'implementation' + }] + }, [_proxy, _impl]); + + return toRet; +} + + +module.exports = async function(deployer) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); + + const _upgrade = async( + _proxy, + _impl + ) => { + const result = await multiSigWallet.submitTransaction( + PROXY_ADMIN, + EMPTY_BYTES, + _encodeUpgradeFunction( + _proxy, + _impl + ) + ) + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + } + + await _upgrade( + PROXY, + IMPLEMENTATION_ADDRESS + ) +} \ No newline at end of file diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js new file mode 100644 index 0000000..a14e244 --- /dev/null +++ b/scripts/units/stableswap-setup.js @@ -0,0 +1,120 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); + +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); + +const STABLE_SWAP_ADDRESS = ""; +const USDDepositAmount = web3.utils.toWei('400000','ether') +const FXDDepositAmount = web3.utils.toWei('400000','ether') +const USDAddress = "" +const FXDAddress = "" +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeDepositFunction = (_token, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'depositToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + },{ + type: 'uint256', + name: '_amount' + }] + }, [_token, _amount]); + + return toRet; +} + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + + const approveUSD = async() =>{ + let resultApproveUSD = await multiSigWallet.submitTransaction( + USDAddress, + EMPTY_BYTES, + _encodeApproveFunction(STABLE_SWAP_ADDRESS,USDDepositAmount), + 0, + {gas: 8000000} + ) + let txIndexApproveUSD = eventsHelper.getIndexedEventArgs(resultApproveUSD, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexApproveUSD, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexApproveUSD, {gas: 8000000}); + } + + const approveFXD = async () => { + + + let resultApproveFXD = await multiSigWallet.submitTransaction( + FXDAddress, + EMPTY_BYTES, + _encodeApproveFunction(STABLE_SWAP_ADDRESS,FXDDepositAmount), + 0, + {gas: 8000000} + ) + + let txIndexApproveFXD = eventsHelper.getIndexedEventArgs(resultApproveFXD, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexApproveFXD, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexApproveFXD, {gas: 8000000}); + } + + + const depositUSD = async() => { + let resultDepositUSD = await multiSigWallet.submitTransaction( + STABLE_SWAP_ADDRESS, + EMPTY_BYTES, + _encodeDepositFunction( + USDAddress, + USDDepositAmount + ),0,{gas: 8000000} + ) + + let txIndexDepositUSD= eventsHelper.getIndexedEventArgs(resultDepositUSD, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexDepositUSD, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexDepositUSD, {gas: 8000000}); + } + + + + const depositFXD = async() =>{ + let resultDepositFXD = await multiSigWallet.submitTransaction( + STABLE_SWAP_ADDRESS, + EMPTY_BYTES, + _encodeDepositFunction( + FXDAddress, + FXDDepositAmount + ),0,{gas: 8000000} + ) + + let txIndexDepositFXD = eventsHelper.getIndexedEventArgs(resultDepositFXD, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(txIndexDepositFXD, {gas: 8000000}); + await multiSigWallet.executeTransaction(txIndexDepositFXD, {gas: 8000000}); + } + + await approveUSD(); + await approveFXD(); + await depositUSD(); + await depositFXD(); + +} From c36cff12d3d56ca4544ef1d0ad1fd7744062e4dd Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 17 Feb 2023 13:57:06 +0545 Subject: [PATCH 11/56] adding scripts --- coralX-scenarios.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 4f48017..fe8b963 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -57,6 +57,16 @@ module.exports = { ['execute', '--path', 'scripts/units/create_stablecoin_open_position.js', '--network', 'apothem'], ], + createUpgradeApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/create-upgrade.js', '--network', 'apothem'], + ], + + setupStableSwapApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/stableswap-setup.js', '--network', 'apothem'], + ], + deployStakingUpgradeApothem: [ ['compile'], ['execute', '--path', 'scripts/upgrades/1_deploy_staking_upgrade.js', '--network', 'apothem'], From a734c455420af03545ba1e3e9ef62e309b6f58b5 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 17 Feb 2023 14:25:52 +0545 Subject: [PATCH 12/56] adding more scenarios --- .gitignore | 2 + coralX-scenarios.js | 5 ++ scripts/units/create-upgrade.js | 9 ++-- .../units/stableswap-daily-limit-update.js | 50 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 scripts/units/stableswap-daily-limit-update.js diff --git a/.gitignore b/.gitignore index b093c1a..4576df2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ privateKey yarn.lock package-lock.json bin +addresses.json + diff --git a/coralX-scenarios.js b/coralX-scenarios.js index fe8b963..c4eaec4 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -71,6 +71,11 @@ module.exports = { ['compile'], ['execute', '--path', 'scripts/upgrades/1_deploy_staking_upgrade.js', '--network', 'apothem'], ], + + updateDailySwapLimitApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/stableswap-daily-limit-update.js', '--network', 'apothem'], + ], migrateAndConfigureForTests: [ ['compile'], ['execute', '--path', 'scripts/migrations/deployment-test'], diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index 4684332..f98faef 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -9,9 +9,10 @@ const EMPTY_BYTES = '0x000000000000000000000000000000000000000000000000000000000 const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const PROXY_ADMIN = "" -const PROXY = "" -const IMPLEMENTATION_ADDRESS = "" +//RIGHT NOW SETUP FOR STAKING +const PROXY_ADMIN = "0x43d97AD756fe2b7E48a2384eD7c400Db37698167" +const PROXY = "0x06F32926169b922F5e885c8a31CB7e60D554A6E6" +const IMPLEMENTATION_ADDRESS = "0xe017a18Ad42abAE2e53F9A70EF037Ce52e2Eb484" const _encodeUpgradeFunction = (_proxy, _impl) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'upgrade', @@ -43,7 +44,7 @@ module.exports = async function(deployer) { _encodeUpgradeFunction( _proxy, _impl - ) + ),0,{gas:8000000} ) const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js new file mode 100644 index 0000000..b599472 --- /dev/null +++ b/scripts/units/stableswap-daily-limit-update.js @@ -0,0 +1,50 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); + +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const STABLE_SWAP_ADDRESS = "" //SET +const DAILY_LIMIT = web3.utils.toWei('100000','ether') //SET + + +const _encodeUpdateDailySwapLimit = (newdailySwapLimit) =>{ + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setDailySwapLimit', + type: 'function', + inputs: [{ + type: 'uint256', + name: 'newdailySwapLimit' + }] + }, [newdailySwapLimit]); + + return toRet; +} + +module.exports = async function(deployer) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); + + const _updateDailySwapLimit = async( + newdailySwapLimit + ) => { + const result = await multiSigWallet.submitTransaction( + STABLE_SWAP_ADDRESS, + EMPTY_BYTES, + _encodeUpdateDailySwapLimit( + newdailySwapLimit + ),0,{gas:8000000} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + } + + await _updateDailySwapLimit(DAILY_LIMIT) +} \ No newline at end of file From 74976cef61cc5ab516eb0ac51653548162e12316 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 17 Feb 2023 17:28:46 +0545 Subject: [PATCH 13/56] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b093c1a..25b63ee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ privateKey yarn.lock package-lock.json bin +addresses.json From ecafd16e228830c059c3f5de28df3e6f38f139b2 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 20 Feb 2023 13:05:01 +0545 Subject: [PATCH 14/56] max lock positions --- contracts/dao/staking/packages/StakingHandler.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 2b53077..04776df 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -25,6 +25,8 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error ZeroPenalty(); error AlreadyInitialized(); error StreamIdZero(); + error BadMaxLockPositions(); + error StreamNotWithdrawn(); constructor() { _disableInitializers(); } @@ -177,7 +179,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if(streamId == 0){ revert StreamIdZero(); } - require(streamTotalUserPendings[streamId] == 0, "nt withdrawn"); + if(streamTotalUserPendings[streamId]!=0){ + revert StreamNotWithdrawn(); + } Stream storage stream = streams[streamId]; require(stream.status == StreamStatus.ACTIVE, "No Stream"); stream.status = StreamStatus.INACTIVE; @@ -358,5 +362,11 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A minLockPeriod = _minLockPeriod; } - + function setMaxLockPositions(uint256 newMaxLockPositions) public onlyRole(DEFAULT_ADMIN_ROLE){ + if(newMaxLockPositions < maxLockPositions){ + revert BadMaxLockPositions(); + } + maxLockPositions = newMaxLockPositions; + } + } From 0154c5aeadc30906481b7cc12f328e82b433fad5 Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 22 Feb 2023 16:38:17 +0545 Subject: [PATCH 15/56] implementing multisig scripts --- config/external-addresses.json | 13 ++ config/newly-generated-transaction-index.json | 1 + config/stablecoin-addresses-proxy-wallet.json | 1 + scripts/units/create_pool_dex.js | 19 ++- scripts/units/create_pool_dex_xdc.js | 22 ++- .../units/create_stablecoin_open_position.js | 33 ++++- .../units/create_stablecoin_proxy_wallet.js | 19 ++- scripts/units/create_stream.js | 118 ++++++++++++++++ scripts/units/execute-proposals.js | 90 +++++++++++++ scripts/units/propose_stream.js | 126 ++++++++++++++++++ .../units/stableswap-daily-limit-update.js | 5 +- scripts/units/stableswap-setup.js | 12 +- 12 files changed, 439 insertions(+), 20 deletions(-) create mode 100644 config/external-addresses.json create mode 100644 config/newly-generated-transaction-index.json create mode 100644 config/stablecoin-addresses-proxy-wallet.json create mode 100644 scripts/units/create_stream.js create mode 100644 scripts/units/execute-proposals.js create mode 100644 scripts/units/propose_stream.js diff --git a/config/external-addresses.json b/config/external-addresses.json new file mode 100644 index 0000000..de2c0a8 --- /dev/null +++ b/config/external-addresses.json @@ -0,0 +1,13 @@ +{ + "positionManager":"0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b", + "stabilityFeeCollector":"0x62889248B6C81D31D7acc450cc0334D0AA58A14A", + "xdcAdapter":"0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1", + "stablecoinAdapter":"0x07a2C89774a3F3c57980AD7A528Aea6F262d8939", + "collateralPoolId":"0x5844430000000000000000000000000000000000000000000000000000000000", + "DEX_ROUTER_ADDRESS":"0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95", + "PROXY_WALLET_REGISTRY_ADDRESS":"0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904", + "STABLE_SWAP_ADDRESS":"", + "USD_ADDRESS":"", + "FXD_ADDRESS":"", + "MAIN_TOKEN_GOVERNOR_ADDRESS":"" +} \ No newline at end of file diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json new file mode 100644 index 0000000..105437e --- /dev/null +++ b/config/newly-generated-transaction-index.json @@ -0,0 +1 @@ +{"addLiquidityTxnIdx":"0x0000000000000000000000000000000000000000000000000000000000000048"} \ No newline at end of file diff --git a/config/stablecoin-addresses-proxy-wallet.json b/config/stablecoin-addresses-proxy-wallet.json new file mode 100644 index 0000000..986d828 --- /dev/null +++ b/config/stablecoin-addresses-proxy-wallet.json @@ -0,0 +1 @@ +{"proxyWallet":"0xf5f4Db6F879F67d729dC1a6E246Cd80B2b037eBa"} \ No newline at end of file diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index b572dfe..d39db97 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -8,6 +8,9 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC @@ -22,7 +25,8 @@ const Amount_B_Minimum = web3.utils.toWei('1', 'ether') // const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') // const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') //What should -const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER +//const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -100,7 +104,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); //Will need to change it once it expires - const deadline = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP//NOTE: Please change it + const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it let resultApprove_A = await multiSigWallet.submitTransaction( Token_A_Address, EMPTY_BYTES, @@ -145,6 +149,17 @@ module.exports = async function(deployer) { let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); + + let addLiquidityTxn = { + addLiquidityTxnIdx: txIndexAddLiquidity + } + let data = JSON.stringify(addLiquidityTxn); + + fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + if(err){ + console.log(err) + } + }) } diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 0212f53..3c90289 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -8,12 +8,16 @@ const EMPTY_BYTES = '0x000000000000000000000000000000000000000000000000000000000 const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('5', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('3', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') -const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router + +//const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS const TOKEN_ETH = web3.utils.toWei('10', 'ether') const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -80,8 +84,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const deadline = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP//NOTE: Please change it - + const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it let resultApprove = await multiSigWallet.submitTransaction( TOKEN_ADDRESS, @@ -110,7 +113,18 @@ module.exports = async function(deployer) { ) let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); + const execResult = await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); + + let addLiquidityTxn = { + addLiquidityTxnIdx: txIndexAddLiquidity + } + let data = JSON.stringify(addLiquidityTxn); + + fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + if(err){ + console.log(err) + } + }) } diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 167f412..2f24163 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -7,16 +7,26 @@ const EMPTY_BYTES = '0x000000000000000000000000000000000000000000000000000000000 const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const rawDataStablecoin = fs.readFileSync('../../stablecoin-addresses.json'); +const rawDataStablecoin = fs.readFileSync('../../config/stablecoin-addresses-proxy-wallet.json'); const addressesStableCoin = JSON.parse(rawDataStablecoin); const XDC_COL = web3.utils.toWei('20','ether') +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + //xdcBe6f6500C3e45a78E17818570b99a7646F8b59F3 const PROXY_WALLET = addressesStableCoin.proxyWallet -const positionMananger = "0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b" -const stabilityFeeCollector = "0x62889248B6C81D31D7acc450cc0334D0AA58A14A" -const xdcAdapter = "0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1" -const stablecoinAdapter = "0x07a2C89774a3F3c57980AD7A528Aea6F262d8939" -const collateralPoolId = '0x5844430000000000000000000000000000000000000000000000000000000000' + +const positionMananger = addressesExternal.positionManager +const stabilityFeeCollector = addressesExternal.stabilityFeeCollector +const xdcAdapter = addressesExternal.xdcAdapter +const stablecoinAdapter = addressesExternal.stablecoinAdapter +const collateralPoolId = addressesExternal.collateralPoolId + +// const positionMananger = "0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b" +// const stabilityFeeCollector = "0x62889248B6C81D31D7acc450cc0334D0AA58A14A" +// const xdcAdapter = "0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1" +// const stablecoinAdapter = "0x07a2C89774a3F3c57980AD7A528Aea6F262d8939" +// const collateralPoolId = '0x5844430000000000000000000000000000000000000000000000000000000000' const stablecoinAmount = web3.utils.toWei('5') const data = "0x00" @@ -103,6 +113,17 @@ module.exports = async function(deployer) { let txExecute = eventsHelper.getIndexedEventArgs(resultExecute, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txExecute, {gas: 8000000}); await multiSigWallet.executeTransaction(txExecute, {gas: 8000000}); + + let openPositionTxn = { + openPositionTxnIdx: txExecute + } + let data = JSON.stringify(openPositionTxn); + + fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + if(err){ + console.log(err) + } + }) } diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index f158c7e..b2bad2c 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -8,8 +8,10 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.sol"); - -const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const PROXY_WALLET_REGISTRY_ADDRESS = addressesExternal.PROXY_WALLET_REGISTRY_ADDRESS +//const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" const _encodeBuildFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'build', @@ -44,7 +46,18 @@ module.exports = async function(deployer) { } let data = JSON.stringify(addressesStableCoin); - fs.writeFileSync('./stablecoin-addresses.json',data, function(err){ + fs.writeFileSync('./config/stablecoin-addresses-proxy-wallet.json',data, function(err){ + if(err){ + console.log(err) + } + }) + + let proxyWalletTxn = { + proxyWalletTxnIdx: txIndexBuild + } + let data2 = JSON.stringify(proxyWalletTxn); + + fs.writeFileSync('./config/newly-generated-transaction-index.json',data2, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/create_stream.js b/scripts/units/create_stream.js new file mode 100644 index 0000000..f531f74 --- /dev/null +++ b/scripts/units/create_stream.js @@ -0,0 +1,118 @@ +const { create } = require('domain'); +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const STREAM_REWARD_TOKEN_ADDRESS = "" +const REWARD_PROPOSAL_AMOUNT = web3.utils.toWei('','ether') +const STREAM_ID = 1//SET +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeCreateStreamFunction = ( + _streamId, _rewardTokenAmount +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'createStream', + type: 'function', + inputs: [{ + type: 'uint256', + name: 'streamId' + },{ + type: 'uint256', + name: 'rewardTokenAmount' + }] + }, [_streamId, _rewardTokenAmount]); + + return toRet; +} + +module.exports = async function(deployer) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); + let approveStreamRewardsTxnIdx; + let createStreamRewardsTxnIdx; + + const _approveStreamRewards = async( + _account, + _amount + ) => { + const result = await multiSigWallet.submitTransaction( + STREAM_REWARD_TOKEN_ADDRESS, + EMPTY_BYTES, + _encodeApproveFunction( + _account, + _amount + ), + 0, + {gas: 8000000} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + approveStreamRewardsTxnIdx = tx; + } + + const _createStreamRewards = async( + _streamId, _rewardTokenAmount + ) => { + const result = await multiSigWallet.submitTransaction( + addresses.staking, + EMPTY_BYTES, + _encodeCreateStreamFunction( + _streamId, + _rewardTokenAmount + ), + 0, + {gas: 8000000} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + createStreamRewardsTxnIdx = tx; + } + + await _approveStreamRewards( + addresses.staking, + REWARD_PROPOSAL_AMOUNT + ) + + await _createStreamRewards( + STREAM_ID, + REWARD_PROPOSAL_AMOUNT + ) + + let streamTxn = { + approveStreamRewardsTxnIdx: approveStreamRewardsTxnIdx, + createStreamRewardsTxnIdx: createStreamRewardsTxnIdx + } + let data = JSON.stringify(streamTxn); + + fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + if(err){ + console.log(err) + } + }) + +} + diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js new file mode 100644 index 0000000..9526bc2 --- /dev/null +++ b/scripts/units/execute-proposals.js @@ -0,0 +1,90 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); +const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +//SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) +const value = EMPTY_BYTES; + +const _encodeExecuteProposal = ( + _targets, + _values, + _calldatas, + _descriptionHash +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'execute', + type: 'function', + inputs: [{ + type: 'address[]', + name: 'targets' + }, + { + type: 'uint256[]', + name: 'values' + }, + { + type: 'bytes[]', + name: 'calldatas' + }, + { + type: 'bytes32', + name: 'descriptionHash' + }] + }, [_targets, + _values, + _calldatas, + _descriptionHash]); + + return toRet; +} +module.exports = async function(deployer) { + //What are the targets to execute + const TARGETS = [] + // What are the values that each target interaction needs. (msg.value for each transaction) + const VALUES = [] + // Calldatas: The function you want to call should be encoded + + // Description + const DESCRIPTION_HASH = '' + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const proxyRegistry = await IProxyRegistry.at(PROXY_WALLET_REGISTRY_ADDRESS) + + const encodedFunctionToCall = '' + const CALLDATAS = [encodedFunctionToCall] + const _executeProposal = async( + _targets, + _values, + _calldatas, + _descriptionHash + ) => { + const result = await multiSigWallet.submitTransaction( + addressesExternal.MAIN_TOKEN_GOVERNOR_ADDRESS, + value, + ( + _targets, + _values, + _calldatas, + _descriptionHash + ),0,{gas:8000000} + ) + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + } + + await _executeProposal( + TARGETS, + VALUES, + CALLDATAS, + DESCRIPTION_HASH + ) +} + diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js new file mode 100644 index 0000000..50e3661 --- /dev/null +++ b/scripts/units/propose_stream.js @@ -0,0 +1,126 @@ +const fs = require('fs'); + +const eventsHelper = require("../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const rawdata = fs.readFileSync('../../addresses.json'); +const addresses = JSON.parse(rawdata); +const REWARD_TOKEN_ADDRESS = "" +const STREAM_OWNER = "" +const MAX_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') +const MIN_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') + + +const _encodeProposeStreamFunction = ( + _owner, + _rewardToken, + _maxDepositedAmount, + _minDepositedAmount, + _scheduleTimes, + _scheduleRewards, + _tau +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'proposeStream', + type: 'function', + inputs: [{ + type: 'address', + name: 'streamOwner' + },{ + type: 'address', + name: 'rewardToken' + },{ + type: 'uint256', + name: 'maxDepositAmount' + },{ + type: 'uint256', + name: 'minDepositAmount' + },{ + type: 'uint256[]', + name: 'scheduleTimes' + },{ + type: 'uint256[]', + name: 'scheduleRewards' + },{ + type: 'uint256', + name: 'tau' + }] + }, [ + _owner, + _rewardToken, + _maxDepositedAmount, + _minDepositedAmount, + _scheduleTimes, + _scheduleRewards, + _tau + ]); + + return toRet; +} + +module.exports = async function(deployer) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); + const startTime = 1676577600 + 500 * 24 * 60 * 60;//1676577600:ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP + const oneYear = 31556926; + const tau = 60; + //SET AS NEEDED + + const scheduleTimes = [ + startTime, + startTime + oneYear, + startTime + 2 * oneYear, + startTime + 3 * oneYear, + startTime + 4 * oneYear, + ]; + //SET AS NEEDED + const scheduleRewards = [ + web3.utils.toWei('20000', 'ether'), + web3.utils.toWei('10000', 'ether'), + web3.utils.toWei('5000', 'ether'), + web3.utils.toWei('2500', 'ether'), + web3.utils.toWei("0", 'ether') + ]; + + const _proposeStreamFromMultiSig = async(_owner, + _rewardToken, + _maxDepositedAmount, + _minDepositedAmount, + _scheduleTimes, + _scheduleRewards, + _tau + ) => + { + const result = await multiSigWallet.submitTransaction( + addresses.staking, + EMPTY_BYTES, + _encodeProposeStreamFunction( + _owner, + _rewardToken, + _maxDepositedAmount, + _minDepositedAmount, + _scheduleTimes, + _scheduleRewards, + _tau + ), + 0, + {gas: 8000000} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + } + + await _proposeStreamFromMultiSig( + STREAM_OWNER, + REWARD_TOKEN_ADDRESS, + MAX_DEPOSIT_AMOUNT, + MIN_DEPOSIT_AMOUNT, + scheduleTimes, + scheduleRewards, + tau + ) +} \ No newline at end of file diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index b599472..1afa975 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -9,7 +9,10 @@ const EMPTY_BYTES = '0x000000000000000000000000000000000000000000000000000000000 const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); -const STABLE_SWAP_ADDRESS = "" //SET +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS +//const STABLE_SWAP_ADDRESS = "" //SET const DAILY_LIMIT = web3.utils.toWei('100000','ether') //SET diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index a14e244..853c0cc 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -9,12 +9,16 @@ const EMPTY_BYTES = '0x000000000000000000000000000000000000000000000000000000000 const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const rawdata = fs.readFileSync('../../addresses.json'); const addresses = JSON.parse(rawdata); - -const STABLE_SWAP_ADDRESS = ""; +const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS +const USDAddress = addressesExternal.USD_ADDRESS +const FXDAddress = addressesExternal.FXD_ADDRESS +//const STABLE_SWAP_ADDRESS = ""; const USDDepositAmount = web3.utils.toWei('400000','ether') const FXDDepositAmount = web3.utils.toWei('400000','ether') -const USDAddress = "" -const FXDAddress = "" +// const USDAddress = "" +// const FXDAddress = "" const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', From 9fa93fa4e4482790880c12567344a221a0ae8299 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 24 Feb 2023 13:34:48 +0400 Subject: [PATCH 16/56] proposal flow test added --- .../dao/governance/proposal-flow.test.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/tests/dao/governance/proposal-flow.test.js b/scripts/tests/dao/governance/proposal-flow.test.js index 1f5d51f..d896877 100644 --- a/scripts/tests/dao/governance/proposal-flow.test.js +++ b/scripts/tests/dao/governance/proposal-flow.test.js @@ -1111,6 +1111,39 @@ describe('Proposal flow', () => { expect(successStatus.toString()).to.equal(TRUE_EVENT_RETURN_IN_HEX) }); + it('Should not have enough vote balance to propose', async() => { + const eightHours = 28800 + await blockchain.increaseTime(eightHours) + encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ + name: 'addSupportingToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [streamReward1.address]); + // create a proposal in MainToken governor + + let errorMessage = "Governor: proposer votes below threshold"; + + await shouldRevert( + mainTokenGovernor.propose( + [box.address], + [0], + [encoded_function], + PROPOSAL_DESCRIPTION, + {"from": NOT_STAKER} + ), + errTypes.revert, + errorMessage + ); + + + // retrieve the proposal id + proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + it('Should blacklist a proposer', async() =>{ const _blacklistAProposer = async(account, blacklistStatus) => { const result = await multiSigWallet.submitTransaction( From 622e7545b749b0b794a12d389c969b1cb82aaf9d Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 2 Mar 2023 14:56:23 +0545 Subject: [PATCH 17/56] voting threshold --- .../deployment/5_deploy_governor.js | 4 +-- .../dao/governance/proposal-flow.test.js | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/migrations/deployment/5_deploy_governor.js b/scripts/migrations/deployment/5_deploy_governor.js index f42b56b..8618330 100644 --- a/scripts/migrations/deployment/5_deploy_governor.js +++ b/scripts/migrations/deployment/5_deploy_governor.js @@ -7,8 +7,8 @@ const VMainToken_address = VMainToken.address; const TimelockController_address = TimelockController.address; const MultiSigWallet_address = MultiSigWallet.address; const initialVotingDelay = 1; -const votingPeriod = 20; -const initialProposalThreshold = 1000; +const votingPeriod = 40; +const initialProposalThreshold = web3.utils.toWei('1000','ether'); const proposalTimeDelay = 2; const proposalLifetime = 86400; diff --git a/scripts/tests/dao/governance/proposal-flow.test.js b/scripts/tests/dao/governance/proposal-flow.test.js index d896877..8c833fd 100644 --- a/scripts/tests/dao/governance/proposal-flow.test.js +++ b/scripts/tests/dao/governance/proposal-flow.test.js @@ -1125,19 +1125,48 @@ describe('Proposal flow', () => { // create a proposal in MainToken governor let errorMessage = "Governor: proposer votes below threshold"; - + await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) + await _transferFromMultiSigTreasury(NOT_STAKER) + await stakingService.createLock(web3.utils.toWei('1','ether'),lockingPeriod,{from: NOT_STAKER}); await shouldRevert( mainTokenGovernor.propose( - [box.address], + [stakingService.address], [0], [encoded_function], - PROPOSAL_DESCRIPTION, + "PROPOSAL_DESCRIPTION", {"from": NOT_STAKER} ), errTypes.revert, errorMessage ); + // retrieve the proposal id + proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + it('Should not have enough vote balance to propose', async() => { + const eightHours = 28800 + await blockchain.increaseTime(eightHours) + encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ + name: 'addSupportingToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [streamReward1.address]); + // create a proposal in MainToken governor + + await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) + await _transferFromMultiSigTreasury(NOT_STAKER) + await stakingService.createLock(T_TO_STAKE,lockingPeriod,{from: NOT_STAKER}); + await mainTokenGovernor.propose( + [stakingService.address], + [0], + [encoded_function], + "PROPOSAL_DESCRIPTION", + {"from": NOT_STAKER} + ), // retrieve the proposal id proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; From 035eead2b87b03693924b31e37d8ccb485afc0a2 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 3 Mar 2023 14:06:55 +0545 Subject: [PATCH 18/56] added scripts for different scenarios --- config/external-addresses.json | 2 +- config/newly-generated-transaction-index.json | 2 +- coralX-scenarios.js | 18 +++- docs/SCENARIOS-INSTRUCTIONS.md | 92 +++++++++++++++++++ scripts/units/create-upgrade.js | 10 +- scripts/units/create_pool_dex.js | 21 ++--- scripts/units/create_pool_dex_xdc.js | 19 ++-- .../units/create_stablecoin_open_position.js | 13 ++- .../units/create_stablecoin_proxy_wallet.js | 14 ++- scripts/units/create_stream.js | 16 ++-- scripts/units/execute-proposals.js | 11 +-- scripts/units/helpers/constants.js | 14 +++ scripts/units/propose-proposal.js | 9 +- scripts/units/propose_stream.js | 8 +- scripts/units/queue-proposal.js | 11 +-- scripts/units/setup-multisig-owners.js | 12 +-- scripts/units/setup_council_stakes.js | 13 +-- .../units/stableswap-daily-limit-update.js | 11 +-- scripts/units/stableswap-setup.js | 22 ++--- scripts/units/transfer-tokens.js | 9 +- 20 files changed, 206 insertions(+), 121 deletions(-) create mode 100644 docs/SCENARIOS-INSTRUCTIONS.md create mode 100644 scripts/units/helpers/constants.js diff --git a/config/external-addresses.json b/config/external-addresses.json index 5681626..5c07e2f 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -8,5 +8,5 @@ "PROXY_WALLET_REGISTRY_ADDRESS":"0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904", "STABLE_SWAP_ADDRESS":"", "USD_ADDRESS":"", - "FXD_ADDRESS":"", + "FXD_ADDRESS":"" } \ No newline at end of file diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index 105437e..b960057 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -1 +1 @@ -{"addLiquidityTxnIdx":"0x0000000000000000000000000000000000000000000000000000000000000048"} \ No newline at end of file +{"addLiquidityTxnIdx":"0x000000000000000000000000000000000000000000000000000000000000004a"} \ No newline at end of file diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 3e60cd0..4c41631 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -81,7 +81,23 @@ module.exports = { ['compile'], ['execute', '--path', 'scripts/units/update-voting-tokens-min-balance.js', '--network', 'apothem'], ], - + + proposeProposalApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/propose-proposal.js', '--network', 'apothem'], + ], + + queueProposalApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/queue-proposal.js', '--network', 'apothem'], + ], + + executeProposalApothem: [ + ['compile'], + ['execute', '--path', 'scripts/units/execute-proposal.js', '--network', 'apothem'], + ], + + migrateAndConfigureForTests: [ ['compile'], ['execute', '--path', 'scripts/migrations/deployment-test'], diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md new file mode 100644 index 0000000..c2967c9 --- /dev/null +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -0,0 +1,92 @@ +**How to Setup Council Stakes: //Note: This is only possible once as theres initializer for council stakes.** + +In file in scripts/units/setup_council_stakes.js + +1. Hardcode COUNCIL_1, COUNCIL_2, COUNCIL_3 +2. Hardcode T_TO_STAKE: Lock position for each council +3. Hardcode ​​T_TOTAL_TO_APPROVE: Total to approve. Should be T_TO_STAKE * Number of councils +4. coralX scenario –run addCouncilStakesApothem + +**How to transfer tokens from the scripts:** +In this file scripts/units/transfer-tokens.js +Hardcode: +1. T_TO_TRANSFER_PLACEHOLDER +2. TRANSFER_TO_ACCOUNT_PLACEHOLDER +3. coralX scenario --run transferTokenFromMultisigApothem + + +**How to Add Owners from the scripts** +1. In scripts/units/setup-multisig-owners.js Hardcode the COUNCIL_1 and COUNCIL_2 +2. coralX scenario --run addOwnersToMultisigApothem + +**How to create dex pool with Native Tokenr** +1. In scripts/units/create_pool_dex_xdc.js Hardcode the followings: +* TOKEN_ADDRESS +* AMOUNT_TOKEN_DESIRED +* AMOUNT_TOKEN_MIN +* AMOUNT_ETH_MIN +* DEX_ROUTER_ADDRESS //this comes from external address +* TOKEN_ETH + +2. coralX scenario --run createDexXDCPoolApothem + +**How to create dex pool with Two tokens** +1. In scripts/units/create_pool_dex.js Hardcode the followings: +* Token_A_Address +* Token_B_Address +* Amount_A_Desired +* Amount_B_Desired +* Amount_A_Minimum +* Amount_B_Minimum +* const DEX_ROUTER_ADDRESS //this comes from external address + +2. coralX scenario --run createDexXDCPoolApothem + +**How to create Proxy Wallet** +#### Note (VIMP): This can be called only once for any address. So, if I call once from multisig, it will create a proxy wallet whose address is stored in 'config/stablecoin-addresses-proxy-wallet.json'. This address is then used to create positions and this address never changes for a particular Multisig or EOA. + +1. PROXY_WALLET_REGISTRY_ADDRESS will be taken from external addresses +2. coralX scenario --run createProxyWalletApothem + +**How to Open Position** +1. In the file create_stablecoin_open_position.js hardcode: +* XDC_COL //how much collateral to give +* stablecoinAmount //stablecoin amount you want to receive +* data //data you want to pass + +**How to propose a proposal** +1. In the file propose-proposal.js Hardcode these: +* What are the targets to propose to + const TARGETS = [] +* What are the values that each target interaction needs. (msg.value for each transaction) + const VALUES = [] +* Calldatas: The function you want to call should be encoded +* Description + const DESCRIPTION_HASH = '' + +2. coralX scenario --run proposeProposalApothem + +**Queue Proposal** +1. In the file queue-proposal.js Hardcode these: +* What are the targets to queue to + const TARGETS = [] +* What are the values that each target interaction needs. (msg.value for each transaction) + const VALUES = [] +* Calldatas: The function you want to call should be encoded +* Description + const DESCRIPTION_HASH = ''//this is bytes32 + +2. coralX scenario --run queueProposalApothem + +**Execute Proposal** + +1. In the file execute-proposal.js Hardcode these: +* What are the targets to execute to + const TARGETS = [] +* What are the values that each target interaction needs. (msg.value for each transaction) + const VALUES = [] +* Calldatas: The function you want to call should be encoded +* Description + const DESCRIPTION_HASH = ''//this is bytes32 + +2. coralX scenario --run proposeProposalApothem diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index f98faef..4b4ac2d 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -1,13 +1,11 @@ const fs = require('fs'); +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); - -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); //RIGHT NOW SETUP FOR STAKING const PROXY_ADMIN = "0x43d97AD756fe2b7E48a2384eD7c400Db37698167" @@ -40,13 +38,13 @@ module.exports = async function(deployer) { ) => { const result = await multiSigWallet.submitTransaction( PROXY_ADMIN, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeUpgradeFunction( _proxy, _impl ),0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index d39db97..9490c2f 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -1,14 +1,13 @@ const fs = require('fs'); +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); @@ -107,31 +106,31 @@ module.exports = async function(deployer) { const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it let resultApprove_A = await multiSigWallet.submitTransaction( Token_A_Address, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), 0, {gas: 8000000} ) - let txIndexApprove_A = eventsHelper.getIndexedEventArgs(resultApprove_A, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApprove_A = eventsHelper.getIndexedEventArgs(resultApprove_A, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApprove_A, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApprove_A, {gas: 8000000}); let resultApprove_B = await multiSigWallet.submitTransaction( Token_B_Address, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_B_Desired), 0, {gas: 8000000} ) - let txIndexApprove_B = eventsHelper.getIndexedEventArgs(resultApprove_B, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApprove_B = eventsHelper.getIndexedEventArgs(resultApprove_B, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApprove_B, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApprove_B, {gas: 8000000}); let resultAddLiquidity = await multiSigWallet.submitTransaction( DEX_ROUTER_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeAddLiqudityFunction( Token_A_Address, Token_B_Address, @@ -146,7 +145,7 @@ module.exports = async function(deployer) { {gas: 8000000} ) - let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); @@ -155,7 +154,7 @@ module.exports = async function(deployer) { } let data = JSON.stringify(addLiquidityTxn); - fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 3c90289..6b97074 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -1,14 +1,11 @@ const fs = require('fs'); - +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); -const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address @@ -88,13 +85,13 @@ module.exports = async function(deployer) { let resultApprove = await multiSigWallet.submitTransaction( TOKEN_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_TOKEN_DESIRED), 0, {gas: 8000000} ) - let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApprove, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); let resultAddLiquidity = await multiSigWallet.submitTransaction( @@ -111,16 +108,16 @@ module.exports = async function(deployer) { 0, {gas: 8000000} ) - let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); - const execResult = await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); + await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); let addLiquidityTxn = { addLiquidityTxnIdx: txIndexAddLiquidity } let data = JSON.stringify(addLiquidityTxn); - fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 2f24163..35fabf6 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -1,16 +1,15 @@ const fs = require('fs'); - +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); + +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const rawDataStablecoin = fs.readFileSync('../../config/stablecoin-addresses-proxy-wallet.json'); const addressesStableCoin = JSON.parse(rawDataStablecoin); const XDC_COL = web3.utils.toWei('20','ether') -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); //xdcBe6f6500C3e45a78E17818570b99a7646F8b59F3 @@ -110,7 +109,7 @@ module.exports = async function(deployer) { {gas: 8000000} ) - let txExecute = eventsHelper.getIndexedEventArgs(resultExecute, SUBMIT_TRANSACTION_EVENT)[0]; + let txExecute = eventsHelper.getIndexedEventArgs(resultExecute, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txExecute, {gas: 8000000}); await multiSigWallet.executeTransaction(txExecute, {gas: 8000000}); @@ -119,7 +118,7 @@ module.exports = async function(deployer) { } let data = JSON.stringify(openPositionTxn); - fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index b2bad2c..1832ac8 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -1,14 +1,12 @@ const fs = require('fs'); - +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.sol"); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); const PROXY_WALLET_REGISTRY_ADDRESS = addressesExternal.PROXY_WALLET_REGISTRY_ADDRESS //const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" @@ -31,13 +29,13 @@ module.exports = async function(deployer) { const proxyRegistry = await IProxyRegistry.at(PROXY_WALLET_REGISTRY_ADDRESS) let resultBuild= await multiSigWallet.submitTransaction( PROXY_WALLET_REGISTRY_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeBuildFunction(multiSigWallet.address), 0, {gas: 8000000} ) - let txIndexBuild = eventsHelper.getIndexedEventArgs(resultBuild, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexBuild = eventsHelper.getIndexedEventArgs(resultBuild, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexBuild, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexBuild, {gas: 8000000}); const proxyWalletAddress = await proxyRegistry.proxies(multiSigWallet.address) @@ -57,7 +55,7 @@ module.exports = async function(deployer) { } let data2 = JSON.stringify(proxyWalletTxn); - fs.writeFileSync('./config/newly-generated-transaction-index.json',data2, function(err){ + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data2, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/create_stream.js b/scripts/units/create_stream.js index f531f74..53ac787 100644 --- a/scripts/units/create_stream.js +++ b/scripts/units/create_stream.js @@ -1,12 +1,10 @@ -const { create } = require('domain'); const fs = require('fs'); +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const STREAM_REWARD_TOKEN_ADDRESS = "" const REWARD_PROPOSAL_AMOUNT = web3.utils.toWei('','ether') @@ -57,7 +55,7 @@ module.exports = async function(deployer) { ) => { const result = await multiSigWallet.submitTransaction( STREAM_REWARD_TOKEN_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction( _account, _amount @@ -66,7 +64,7 @@ module.exports = async function(deployer) { {gas: 8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); approveStreamRewardsTxnIdx = tx; @@ -77,7 +75,7 @@ module.exports = async function(deployer) { ) => { const result = await multiSigWallet.submitTransaction( addresses.staking, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeCreateStreamFunction( _streamId, _rewardTokenAmount @@ -86,7 +84,7 @@ module.exports = async function(deployer) { {gas: 8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); createStreamRewardsTxnIdx = tx; @@ -108,7 +106,7 @@ module.exports = async function(deployer) { } let data = JSON.stringify(streamTxn); - fs.writeFileSync('./config/newly-generated-transaction-index.json',data, function(err){ + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ if(err){ console.log(err) } diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index 2121c78..83dc7bf 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -1,17 +1,14 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); -const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); //SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) -const value = EMPTY_BYTES; +const value = constants.EMPTY_BYTES; const _encodeExecuteProposal = ( _targets, @@ -87,7 +84,7 @@ module.exports = async function(deployer) { _descriptionHash ),0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/helpers/constants.js b/scripts/units/helpers/constants.js new file mode 100644 index 0000000..32976c1 --- /dev/null +++ b/scripts/units/helpers/constants.js @@ -0,0 +1,14 @@ +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const PATH_TO_ADDRESSES= '../../addresses.json' +const PATH_TO_ADDRESSES_EXTERNAL = '../../config/external-addresses.json' +const PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX = './config/newly-generated-transaction-index.json' + + +module.exports = { + SUBMIT_TRANSACTION_EVENT, + EMPTY_BYTES, + PATH_TO_ADDRESSES, + PATH_TO_ADDRESSES_EXTERNAL, + PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX +} diff --git a/scripts/units/propose-proposal.js b/scripts/units/propose-proposal.js index 0c120d6..bb46632 100644 --- a/scripts/units/propose-proposal.js +++ b/scripts/units/propose-proposal.js @@ -1,15 +1,12 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); -const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); //SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) -const value = EMPTY_BYTES; +const value = constants.EMPTY_BYTES; const _encodeProposalFunction = ( _targets, @@ -84,7 +81,7 @@ module.exports = async function(deployer) { _descriptionHash ),0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 50e3661..38b8448 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -3,9 +3,7 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const REWARD_TOKEN_ADDRESS = "" const STREAM_OWNER = "" @@ -95,7 +93,7 @@ module.exports = async function(deployer) { { const result = await multiSigWallet.submitTransaction( addresses.staking, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeProposeStreamFunction( _owner, _rewardToken, @@ -109,7 +107,7 @@ module.exports = async function(deployer) { {gas: 8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/queue-proposal.js b/scripts/units/queue-proposal.js index fe5aca1..20adbcf 100644 --- a/scripts/units/queue-proposal.js +++ b/scripts/units/queue-proposal.js @@ -1,15 +1,12 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); -const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); //SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) -const value = EMPTY_BYTES; +const value = constants.EMPTY_BYTES; const _encodeQueueFunction = ( _targets, @@ -63,7 +60,7 @@ module.exports = async function(deployer) { // Calldatas: The function you want to call should be encoded // Description - const DESCRIPTION_HASH = '' + const DESCRIPTION_HASH = ''//note bytes32 const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const encodedFunctionToCall = _encodedFucntionToCall() @@ -84,7 +81,7 @@ module.exports = async function(deployer) { _descriptionHash ),0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 9171265..7dea521 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -1,16 +1,12 @@ //NOTE: Do this at the very end as other scripts wont execute after this is done due to owners signatures requirement const fs = require('fs'); - +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); - -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; - const COUNCIL_1_PLACEHOLDER = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; const COUNCIL_2_PLACEHOLDER = "0x01d2D3da7a42F64e7Dc6Ae405F169836556adC86"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const _encodeAddOwnersFunction = (_accounts) => { @@ -34,13 +30,13 @@ module.exports = async function(deployer) { let result = await multiSigWallet.submitTransaction( MULTISIG_WALLET_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeAddOwnersFunction([COUNCIL_1_PLACEHOLDER, COUNCIL_2_PLACEHOLDER]), 0, {gas: 8000000} ); - txIndex = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + txIndex = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index 272f564..ae86cb4 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -7,9 +7,6 @@ const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; - const LOCK_PERIOD = 365 * 24 * 60 * 60; //SET AS NEEDED // this needs to be sum of all the stakes. Right now 10KK * 3. @@ -22,7 +19,7 @@ const COUNCIL_1 = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; const COUNCIL_2 = "0x01d2D3da7a42F64e7Dc6Ae405F169836556adC86"; const COUNCIL_3 = "0x4C5F0f90a2D4b518aFba11E22AC9b8F6B031d204"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const _createLockParamObject = ( @@ -75,13 +72,13 @@ module.exports = async function(deployer) { let resultApprove = await multiSigWallet.submitTransaction( addresses.fthmToken, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(stakingService.address,T_TOTAL_TO_APPROVE), 0, {gas: 8000000} ) - let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApprove, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); const LockParamObjectForAllCouncils = [ @@ -92,14 +89,14 @@ module.exports = async function(deployer) { let resultCreateLock = await multiSigWallet.submitTransaction( stakingService.address, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeCreateLocksForCouncils(LockParamObjectForAllCouncils), 0, {gas: 8000000} ) - let txIndexCreateLock = eventsHelper.getIndexedEventArgs(resultCreateLock, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexCreateLock = eventsHelper.getIndexedEventArgs(resultCreateLock, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexCreateLock, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexCreateLock, {gas: 8000000}); } \ No newline at end of file diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index 1afa975..5d250f3 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -4,12 +4,9 @@ const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); - -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS //const STABLE_SWAP_ADDRESS = "" //SET @@ -38,13 +35,13 @@ module.exports = async function(deployer) { ) => { const result = await multiSigWallet.submitTransaction( STABLE_SWAP_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeUpdateDailySwapLimit( newdailySwapLimit ),0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); } diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index 853c0cc..f07cebc 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -5,11 +5,9 @@ const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync('../../config/external-addresses.json'); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS const USDAddress = addressesExternal.USD_ADDRESS @@ -57,12 +55,12 @@ module.exports = async function(deployer) { const approveUSD = async() =>{ let resultApproveUSD = await multiSigWallet.submitTransaction( USDAddress, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(STABLE_SWAP_ADDRESS,USDDepositAmount), 0, {gas: 8000000} ) - let txIndexApproveUSD = eventsHelper.getIndexedEventArgs(resultApproveUSD, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApproveUSD = eventsHelper.getIndexedEventArgs(resultApproveUSD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApproveUSD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApproveUSD, {gas: 8000000}); } @@ -72,13 +70,13 @@ module.exports = async function(deployer) { let resultApproveFXD = await multiSigWallet.submitTransaction( FXDAddress, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeApproveFunction(STABLE_SWAP_ADDRESS,FXDDepositAmount), 0, {gas: 8000000} ) - let txIndexApproveFXD = eventsHelper.getIndexedEventArgs(resultApproveFXD, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexApproveFXD = eventsHelper.getIndexedEventArgs(resultApproveFXD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApproveFXD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApproveFXD, {gas: 8000000}); } @@ -87,14 +85,14 @@ module.exports = async function(deployer) { const depositUSD = async() => { let resultDepositUSD = await multiSigWallet.submitTransaction( STABLE_SWAP_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeDepositFunction( USDAddress, USDDepositAmount ),0,{gas: 8000000} ) - let txIndexDepositUSD= eventsHelper.getIndexedEventArgs(resultDepositUSD, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexDepositUSD= eventsHelper.getIndexedEventArgs(resultDepositUSD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexDepositUSD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexDepositUSD, {gas: 8000000}); } @@ -104,14 +102,14 @@ module.exports = async function(deployer) { const depositFXD = async() =>{ let resultDepositFXD = await multiSigWallet.submitTransaction( STABLE_SWAP_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeDepositFunction( FXDAddress, FXDDepositAmount ),0,{gas: 8000000} ) - let txIndexDepositFXD = eventsHelper.getIndexedEventArgs(resultDepositFXD, SUBMIT_TRANSACTION_EVENT)[0]; + let txIndexDepositFXD = eventsHelper.getIndexedEventArgs(resultDepositFXD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexDepositFXD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexDepositFXD, {gas: 8000000}); } diff --git a/scripts/units/transfer-tokens.js b/scripts/units/transfer-tokens.js index 2d06bac..bca29c1 100644 --- a/scripts/units/transfer-tokens.js +++ b/scripts/units/transfer-tokens.js @@ -4,13 +4,10 @@ const eventsHelper = require("../tests/helpers/eventsHelper"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; - const T_TO_TRANSFER_PLACEHOLDER = web3.utils.toWei('10000000','ether') //SET AS NEEDED const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x4C5F0f90a2D4b518aFba11E22AC9b8F6B031d204" //SET AS NEEDED -const rawdata = fs.readFileSync('../../addresses.json'); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const _encodeTransferFunction = (_account, t_to_stake) => { @@ -39,12 +36,12 @@ module.exports = async function(deployer) { const _transferFromMultiSigTreasury = async (_account, _value) => { const result = await multiSigWallet.submitTransaction( FATHOM_TOKEN_ADDRESS, - EMPTY_BYTES, + constants.EMPTY_BYTES, _encodeTransferFunction(_account, _value), 0, {gas: 8000000} ); - txIndex = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + txIndex = eventsHelper.getIndexedEventArgs(result, constants.EMPTY_BYTES)[0]; await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); } From e73e9fb7d39d47091581e53e26aee04495d96def Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 3 Mar 2023 14:50:15 +0545 Subject: [PATCH 19/56] merging reaudit-fixes-final --- .../staking/helpers/StakingGettersHelper.sol | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/contracts/dao/staking/helpers/StakingGettersHelper.sol b/contracts/dao/staking/helpers/StakingGettersHelper.sol index 389e079..5065930 100644 --- a/contracts/dao/staking/helpers/StakingGettersHelper.sol +++ b/contracts/dao/staking/helpers/StakingGettersHelper.sol @@ -10,7 +10,6 @@ import "../../../common/access/AccessControl.sol"; contract StakingGettersHelper is IStakingGetterHelper, AccessControl { address private stakingContract; - //uint256 public constant WEIGHT_SLOT = 14; // the storage slot in staking contract where WEIGHT resides constructor(address _stakingContract, address admin) { stakingContract = _stakingContract; _grantRole(DEFAULT_ADMIN_ROLE, admin); @@ -119,30 +118,7 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { (weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) / maxLockPeriod); } - function _getWeight() internal view returns (Weight memory) { - //bytes32 weight = IStakingHelper(stakingContract).readBySlot(WEIGHT_SLOT); - // uint32 penaltyWeightMultiplier; - // uint32 minWeightPenalty; - // uint32 maxWeightPenalty; - // uint32 minWeightShares; - // uint32 maxWeightShares; - // assembly { - // let value := weight - // maxWeightShares := and(0xffffffff, value) - // //shift right by 32 then, do and by 32 bits to get the value - // let minWeightShares_shifted:= shr(32,value) - // minWeightShares := and(0xffffffff, minWeightShares_shifted) - // //shift right by 64 then, do and by 32 bits to get the value - // let maxWeightPenalty_shifted := shr(64,value) - // maxWeightPenalty := and(0xffffffff, maxWeightPenalty_shifted) - // //shift right by 96 then, do and by 32 bits to get the value - // let minWeightPenalty_shifted := shr(96,value) - // minWeightPenalty := and(0xffffffff, minWeightPenalty_shifted) - // //shift right by 128 then, do and by 32 bits to get the value - // let penaltyWeightMultiplier_shifted := shr(128,value) - // penaltyWeightMultiplier := and(0xffffffff, penaltyWeightMultiplier_shifted) - // } - + function _getWeight() internal view returns (Weight memory) { return IStakingStorage(stakingContract).weight(); } } From 0a45f01a47e47a249222cc820799aa28527009ca Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 3 Mar 2023 18:00:10 +0545 Subject: [PATCH 20/56] adding scripts to store txn index --- config/external-addresses.json | 16 +++++------ scripts/units/create-upgrade.js | 11 ++++++++ .../units/create_stablecoin_open_position.js | 7 +---- scripts/units/execute-proposals.js | 11 ++++++++ scripts/units/helpers/constants.js | 1 - scripts/units/propose-proposal.js | 11 ++++++++ scripts/units/propose_stream.js | 11 ++++++++ scripts/units/queue-proposal.js | 11 ++++++++ .../units/stableswap-daily-limit-update.js | 11 ++++++++ scripts/units/stableswap-setup.js | 27 ++++++++++++++++--- 10 files changed, 98 insertions(+), 19 deletions(-) diff --git a/config/external-addresses.json b/config/external-addresses.json index 5c07e2f..7e5e1d5 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -1,12 +1,12 @@ { - "positionManager":"0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b", - "stabilityFeeCollector":"0x62889248B6C81D31D7acc450cc0334D0AA58A14A", - "xdcAdapter":"0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1", - "stablecoinAdapter":"0x07a2C89774a3F3c57980AD7A528Aea6F262d8939", + "positionManager":"0xa14385249806Fa27Bed76CCE444845aF97C1B5f9", + "stabilityFeeCollector":"0xbe169d7280D789159e4F4a38626818fbA76c0B19", + "xdcAdapter":"0x61D0f739Ab0f199607024Fe589cBBdC1bB90293F", + "stablecoinAdapter":"0x176486E65FEAEc0B831C9254154757B0e2676668", "collateralPoolId":"0x5844430000000000000000000000000000000000000000000000000000000000", "DEX_ROUTER_ADDRESS":"0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95", - "PROXY_WALLET_REGISTRY_ADDRESS":"0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904", - "STABLE_SWAP_ADDRESS":"", - "USD_ADDRESS":"", - "FXD_ADDRESS":"" + "PROXY_WALLET_REGISTRY_ADDRESS":"0x06063CeB65f66A678812e753785D00237F60564A", + "STABLE_SWAP_ADDRESS":"0xa47232D1c5608996D4168222c45ed17E3947a50a", + "USD_ADDRESS":"0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f", + "FXD_ADDRESS":"0x429758F17E06eD5cF60f4382442592021158B578" } \ No newline at end of file diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index 4b4ac2d..6f6bede 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -47,6 +47,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let upgradeTxn = { + upgradeTxnIdx: tx + } + + let data = JSON.stringify(upgradeTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _upgrade( diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 35fabf6..03ccade 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -124,9 +124,4 @@ module.exports = async function(deployer) { } }) -} - - - -//{"proxyFactory":"0x2CF7cAe946D97232b4bA0a819B2d5c753fb6C2C5","simplePriceFeedUSDT":"0x981B8c630C46A6AD21686C58a57E2AFD7362d85e","fixedSpreadLiquidationStrategy":"0x754f0573a97cDEe2e47920a582C228e8b0A63a03","proxyWalletRegistry":"0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904","stabilityFeeCollector":"0x62889248B6C81D31D7acc450cc0334D0AA58A14A", -//"stablecoinAdapter":"0x07a2C89774a3F3c57980AD7A528Aea6F262d8939","showStopper":"0xce3d5feC112C34da459Decfec1bCeF3a6437A5Bc","priceOracle":"0xbCB7f95718d2eB899bB16B861c1e5416EAD36264","fathomStablecoin":"0x08B5860daD9947677F2a9d7DE563cBec9980E44c","positionManager":"0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b","systemDebtEngine":"0xFd28A89440e005562d0E8CD86C4574021C904FD9","liquidationEngine":"0x73EA9Acb76fC83c3BE5A75F5c6940EA623746Ed4","bookKeeper":"0xf5486A08a1528f97cc7da3eED9050DB2387ADf66","collateralPoolConfig":"0x279750eb5799010A295119f38A32A67723a9e10F","accessControlConfig":"0xaa96E05E5f4a024aaAF399A26F0D4341E420269e","flashMintModule":"0x39FAaef7F24B775fe0dDEB5fc62D819723398f65","stableSwapModule":"0x9F1c95900ca81b46F46cDce9C1762048cBc50CC6","flashMintArbitrager":"0xd7dA7B3F5fA605CDD77F09dB37d07fBc5c680fD0","bookKeeperFlashMintArbitrager":"0x00d42C140C2c448D0a7c1E1562D28D8733621c8E","dexPriceOracle":"0x23Bb26c4a59B28ad496bD3140AC7C95869F35830","proxyWalletFactory":"0x198bcA10F74caDAe58656B223e1D279686E1E672","fathomStablecoinProxyActions":"0x233380A9C66CCB6d2aC2BaEEAde5a509De2bb649","ankrCollateralAdapter":"0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1","delayFathomOraclePriceFeed":"0xd2b0E79136E471bf981559A6cE862f793C81701d"}{"51":{"WXDC":"0xE99500AB4A413164DA49Af83B9824749059b46ce","USD":"0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f","FTHM":"0x0000000000000000000000000000000000000000","DEXFactory":"0xe350508951929D3e19222824F41790621fb18A15", "xdcPool":"0xd458788DD7d2fDbB5238d9eeb0a49732BffF08b7","aXDCc":"0xe27990d8c950038C548E6f4BD0657aCE27495D48"} \ No newline at end of file +} \ No newline at end of file diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index 83dc7bf..adcb662 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -87,6 +87,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let executeProposalTxn = { + executeProposalTxnIdx: tx + } + + let data = JSON.stringify(executeProposalTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _executeProposal( diff --git a/scripts/units/helpers/constants.js b/scripts/units/helpers/constants.js index 32976c1..58d348b 100644 --- a/scripts/units/helpers/constants.js +++ b/scripts/units/helpers/constants.js @@ -4,7 +4,6 @@ const PATH_TO_ADDRESSES= '../../addresses.json' const PATH_TO_ADDRESSES_EXTERNAL = '../../config/external-addresses.json' const PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX = './config/newly-generated-transaction-index.json' - module.exports = { SUBMIT_TRANSACTION_EVENT, EMPTY_BYTES, diff --git a/scripts/units/propose-proposal.js b/scripts/units/propose-proposal.js index bb46632..07d8a0b 100644 --- a/scripts/units/propose-proposal.js +++ b/scripts/units/propose-proposal.js @@ -84,6 +84,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let proposeProposalTxn = { + proposeProposalTxnIdx: tx + } + + let data = JSON.stringify(proposeProposalTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _proposeProposal( diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 38b8448..3d07d97 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -110,6 +110,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let proposeStreamTxn = { + proposeStreamTxnIdx: tx + } + + let data = JSON.stringify(proposeStreamTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _proposeStreamFromMultiSig( diff --git a/scripts/units/queue-proposal.js b/scripts/units/queue-proposal.js index 20adbcf..b63c408 100644 --- a/scripts/units/queue-proposal.js +++ b/scripts/units/queue-proposal.js @@ -84,6 +84,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let queueProposalTxn = { + queueProposalTxnIdx: tx + } + + let data = JSON.stringify(queueProposalTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _queueProposal( diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index 5d250f3..6c50301 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -44,6 +44,17 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + + let stableSwapDailyLimitUpdate = { + stableSwapDailyLimitUpdateIdx: tx + } + + let data = JSON.stringify(stableSwapDailyLimitUpdate) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } await _updateDailySwapLimit(DAILY_LIMIT) diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index f07cebc..89073be 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -63,6 +63,7 @@ module.exports = async function(deployer) { let txIndexApproveUSD = eventsHelper.getIndexedEventArgs(resultApproveUSD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApproveUSD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApproveUSD, {gas: 8000000}); + return txIndexApproveUSD } const approveFXD = async () => { @@ -79,6 +80,7 @@ module.exports = async function(deployer) { let txIndexApproveFXD = eventsHelper.getIndexedEventArgs(resultApproveFXD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexApproveFXD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexApproveFXD, {gas: 8000000}); + return txIndexApproveFXD } @@ -95,6 +97,7 @@ module.exports = async function(deployer) { let txIndexDepositUSD= eventsHelper.getIndexedEventArgs(resultDepositUSD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexDepositUSD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexDepositUSD, {gas: 8000000}); + return txIndexDepositUSD } @@ -112,11 +115,27 @@ module.exports = async function(deployer) { let txIndexDepositFXD = eventsHelper.getIndexedEventArgs(resultDepositFXD, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexDepositFXD, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexDepositFXD, {gas: 8000000}); + return txIndexDepositFXD + + } + + let txIndexApproveUSD = await approveUSD(); + let txIndexApproveFXD = await approveFXD(); + let txIndexDepositUSD = await depositUSD(); + let txIndexDepositFXD = await depositFXD(); + + let stableSwapTxn = { + txIndexApproveUSD: txIndexApproveUSD, + txIndexApproveFXD: txIndexApproveFXD, + txIndexDepositUSD: txIndexDepositUSD, + txIndexDepositFXD: txIndexDepositFXD, } - await approveUSD(); - await approveFXD(); - await depositUSD(); - await depositFXD(); + let data = JSON.stringify(stableSwapTxn) + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) } From 70aa1832ea4e185986715a19ed86566b6d3b3478 Mon Sep 17 00:00:00 2001 From: ssubik Date: Tue, 7 Mar 2023 15:09:40 +0545 Subject: [PATCH 21/56] changing to verifyCalLResult to have some error message if Multisig call to other contract fails --- contracts/dao/treasury/MultiSigWallet.sol | 2 +- scripts/migrations/prod/5_init_main_stream.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index 4eb86b6..5145f88 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -226,7 +226,7 @@ contract MultiSigWallet is IMultiSigWallet { if (success) { emit ExecuteTransaction(msg.sender, _txIndex); } else { - revert TransactionRevered(data); + Address.verifyCallResult(success, data, "executeTransaction: reverted without reason"); } } diff --git a/scripts/migrations/prod/5_init_main_stream.js b/scripts/migrations/prod/5_init_main_stream.js index 315810f..e34aedd 100644 --- a/scripts/migrations/prod/5_init_main_stream.js +++ b/scripts/migrations/prod/5_init_main_stream.js @@ -51,7 +51,7 @@ const _encodeInitMainStreamFunction = (_owner, _scheduleTimes, _scheduleRewards, module.exports = async function(deployer) { - const startTime = 1676577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP + const startTime = 1776577600 //ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP const oneMonth = 2628288 const scheduleTimes = [ startTime, From a263f82c7d92398ef09384b4690f8e84572c00c7 Mon Sep 17 00:00:00 2001 From: ssubik Date: Tue, 7 Mar 2023 21:58:05 +0545 Subject: [PATCH 22/56] setting contracts up for review and making some cleanups to tests --- contracts/dao/governance/Governor.sol | 28 +- .../dao/staking/packages/StakingHandler.sol | 1 + scripts/migrations/prod/5_init_main_stream.js | 6 +- .../dao/governance/proposal-flow.test.js | 748 +++++++++++------- scripts/tests/dao/staking/staking.test.js | 2 +- 5 files changed, 479 insertions(+), 306 deletions(-) diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index c9d3dd5..a2a2491 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -107,8 +107,8 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { ) public payable virtual override returns (uint256) { require(live == 1,"not live"); uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - requireNotExpired(proposalId); - requireConfirmed(proposalId); + _requireNotExpired(proposalId); + _requireConfirmed(proposalId); ProposalState status = state(proposalId); require(status == ProposalState.Queued, "Governor: proposal not successful"); @@ -197,12 +197,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { string memory description ) public virtual override returns (uint256) { require(live == 1,"not live"); - require(getVotes(_msgSender(), block.number - 1) >= proposalThreshold(), "Governor: proposer votes below threshold"); + require(getVotes(_msgSender(), block.number - 1) >= proposalThreshold(), + "Governor: proposer votes below threshold"); require(!isBlacklisted[msg.sender],"Proposer is blacklisted"); - require(block.timestamp > nextAcceptableProposalTimestamp[msg.sender], "Can submit in interval"); - - nextAcceptableProposalTimestamp[msg.sender] = block.timestamp + proposalTimeDelay; + _checkNextProposalDelayPassed(msg.sender); uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); @@ -224,13 +223,13 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { proposalIds.push(proposalId); - emit ProposalCreated(proposalId, _msgSender(), targets, values, new string[](targets.length), calldatas, snapshot, deadline, description); - + emit ProposalCreated(proposalId, _msgSender(), targets, values, new string[](targets.length), + calldatas, snapshot, deadline, description); return proposalId; } function confirmProposal(uint256 _proposalId) public onlyMultiSig notExecuted(_proposalId) notConfirmed(_proposalId) { - requireNotExpired(_proposalId); + _requireNotExpired(_proposalId); isConfirmed[_proposalId] = true; ProposalState status = state(_proposalId); require(status == ProposalState.Succeeded || status == ProposalState.Queued, "proposal not successful"); @@ -238,7 +237,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } function revokeConfirmation(uint256 _proposalId) public onlyMultiSig notExecuted(_proposalId) { - requireConfirmed(_proposalId); + _requireConfirmed(_proposalId); isConfirmed[_proposalId] = false; emit RevokeConfirmation(msg.sender, _proposalId); } @@ -600,11 +599,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return (_proposalIds, _descriptionsArray, _statusses); } - function requireConfirmed(uint256 _proposalId) internal view { + function _requireConfirmed(uint256 _proposalId) internal view { require(isConfirmed[_proposalId], "proposal not confirmed"); } - function requireNotExpired(uint256 _proposalId) internal view { + function _requireNotExpired(uint256 _proposalId) internal view { require(_proposals[_proposalId].expireTimestamp >= block.timestamp,"proposal expired"); } @@ -612,6 +611,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return address(this); } + function _checkNextProposalDelayPassed(address account) internal { + require(block.timestamp > nextAcceptableProposalTimestamp[account], "Can submit only after certain delay"); + nextAcceptableProposalTimestamp[account] = block.timestamp + proposalTimeDelay; + } + function _quorumReached(uint256 proposalId) internal view virtual returns (bool); function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 5eeb566..7daea0a 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -369,4 +369,5 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } maxLockPositions = newMaxLockPositions; } + } diff --git a/scripts/migrations/prod/5_init_main_stream.js b/scripts/migrations/prod/5_init_main_stream.js index e34aedd..e56a168 100644 --- a/scripts/migrations/prod/5_init_main_stream.js +++ b/scripts/migrations/prod/5_init_main_stream.js @@ -6,7 +6,8 @@ const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; - +const EXECUTE_TRANSACTION_EVENT = "ExecuteTransaction(address,uint256)"; + const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -89,5 +90,6 @@ module.exports = async function(deployer) { let txIndexInit = eventsHelper.getIndexedEventArgs(resultInit, SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexInit, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexInit, {gas: 10000000}); + let resultExecute = await multiSigWallet.executeTransaction(txIndexInit, {gas: 10000000}); + eventsHelper.getIndexedEventArgs(resultExecute, EXECUTE_TRANSACTION_EVENT)[0] } diff --git a/scripts/tests/dao/governance/proposal-flow.test.js b/scripts/tests/dao/governance/proposal-flow.test.js index 8c833fd..8368da4 100644 --- a/scripts/tests/dao/governance/proposal-flow.test.js +++ b/scripts/tests/dao/governance/proposal-flow.test.js @@ -15,7 +15,8 @@ const NEW_STORE_VALUE = "5"; // / proposal 2 const PROPOSAL_DESCRIPTION_2 = "Proposal #2: Distribute funds from treasury to accounts[5]"; const AMOUNT_OUT_TREASURY = "1000"; - +const SUCCEEDED_PROPOSAL_STATE = "4" +const DEFEATED_PROPOSAL_STATE = "3" // Events const PROPOSAL_CREATED_EVENT = "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)" const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; @@ -59,12 +60,14 @@ const _encodeBlacklistProposer = (_account,_blacklistStatus) =>{ },[_account,_blacklistStatus]); } -const T_TO_STAKE = web3.utils.toWei('2000', 'ether'); +const T_TO_STAKE = web3.utils.toWei('50000', 'ether'); const STAKED_MIN = web3.utils.toWei('1900', 'ether'); let streamReward1; let proposalIdForAddingSupportedToken; let proposalIdForERC20elayFunction let proposalIdForETHRelayFunction +let proposalIdToFailWithoutEnoughVotes + const STAKER_1 = accounts[5]; const STAKER_2 = accounts[6]; const NOT_STAKER = accounts[7]; @@ -365,7 +368,7 @@ describe('Proposal flow', () => { nextBlock++; } - expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal("4"); + expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal(SUCCEEDED_PROPOSAL_STATE); }); @@ -394,7 +397,7 @@ describe('Proposal flow', () => { await multiSigWallet.executeTransaction(txIndex1, {"from": accounts[0]}); }); - it('Queue the proposal', async() => { + it('Queue the proposal and do not wait for minDelay and try to execute', async() => { // Functions mainTokenGovernor.propose and mainTokenGovernor.queue have the same input, except for the // description parameter, which we need to hash. @@ -412,18 +415,54 @@ describe('Proposal flow', () => { description_hash, {"from": accounts[0]} ); + expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal("5"); + const errorMessage = "TimelockController: operation is not ready"; + + await shouldRevert( + mainTokenGovernor.execute( + [box.address], + [0], + [encoded_function], + description_hash, + {"from": accounts[0]} + ), + errTypes.revert, + errorMessage + ); + + }); + + it('Wait for 15 blocks and still fail and then another 15 blocks', async() => { const currentNumber = await web3.eth.getBlockNumber(); const block = await web3.eth.getBlock(currentNumber); const timestamp = block.timestamp; var nextBlock = 1; - while (nextBlock <= 40) { + + while (nextBlock <= 15) { await blockchain.mineBlock(timestamp + nextBlock); nextBlock++; } - expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal("5"); - }); + const errorMessage = "TimelockController: operation is not ready"; + + await shouldRevert( + mainTokenGovernor.execute( + [box.address], + [0], + [encoded_function], + description_hash, + {"from": accounts[0]} + ), + errTypes.revert, + errorMessage + ); + + while (nextBlock <= 30) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + }) it('Execute the proposal', async() => { @@ -479,8 +518,8 @@ describe('Proposal flow', () => { } it('Create proposal to send VC funds from MultiSig treasury to account 5', async() => { - const eightHours = 28800 - await blockchain.mineBlock(await _getTimeStamp() + eightHours); + const proposalTimeDelay = 5 + await blockchain.mineBlock(await _getTimeStamp() + proposalTimeDelay); // create a proposal in MainToken governor result = await mainTokenGovernor.propose( [multiSigWallet.address], @@ -495,6 +534,23 @@ describe('Proposal flow', () => { }); + it('Should revert on creating another proposal too soon', async() => { + await blockchain.mineBlock(await _getTimeStamp() + 1); + const errorMessage = 'Can submit only after certain delay' + + await shouldRevert( + mainTokenGovernor.propose( + [multiSigWallet.address], + [0], + [encoded_treasury_function], + PROPOSAL_DESCRIPTION, + {"from": STAKER_1} + ), + errTypes.revert, + errorMessage + ) + }); + it("Should retrieve voting weights", async () => { const currentNumber = await web3.eth.getBlockNumber(); @@ -560,7 +616,7 @@ describe('Proposal flow', () => { nextBlock++; } // Check that the proposal is succesful: - expect((await mainTokenGovernor.state(proposalId2)).toString()).to.equal("4"); + expect((await mainTokenGovernor.state(proposalId2)).toString()).to.equal(SUCCEEDED_PROPOSAL_STATE); }); @@ -607,6 +663,7 @@ describe('Proposal flow', () => { description_hash_2, {"from": accounts[0]} ); + expect((await mainTokenGovernor.state(proposalId2)).toString()).to.equal("5"); const currentNumber = await web3.eth.getBlockNumber(); const block = await web3.eth.getBlock(currentNumber); @@ -617,7 +674,7 @@ describe('Proposal flow', () => { await blockchain.mineBlock(timestamp + nextBlock); nextBlock++; } - expect((await mainTokenGovernor.state(proposalId2)).toString()).to.equal("5"); + }); @@ -670,29 +727,182 @@ describe('Proposal flow', () => { await _stakeMainGetVe(accounts[9]); await _stakeMainGetVe(accounts[9]); + }); }); - it('Propose to add supporting Token', async() => { - const eightHours = 28800 - await blockchain.increaseTime(eightHours) - encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ - name: 'addSupportingToken', + + describe('#Add Supporting Tokens to the Governor', () => { + it('Propose to add supporting Token', async() => { + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ + name: 'addSupportingToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [streamReward1.address]); + // create a proposal in MainToken governor + result = await mainTokenGovernor.propose( + [mainTokenGovernor.address], + [0], + [encoded_function_add_supporting_token], + PROPOSAL_DESCRIPTION, + {"from": STAKER_1} + ); + // retrieve the proposal id + proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + it('Wait two blocks and then check that the proposal status is: Active', async() => { + + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 2) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + // Check that the proposal is open for voting + expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal("1"); + }); + + it('Vote on the proposal', async() => { + + // enum VoteType { + // Against, + // For, + // Abstain + // } + // => 0 = Against, 1 = For, 2 = Abstain + + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 2) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + // Vote: + await mainTokenGovernor.castVote(proposalIdForAddingSupportedToken, "1", {"from": STAKER_1}); + }); + + it('Wait 40 blocks and then check that the proposal status is: Succeeded', async() => { + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 40) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + + expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal(SUCCEEDED_PROPOSAL_STATE); + }); + + + it('Create multiSig transaction to confirm proposal for adding supported tokens', async() => { + encodedConfirmation1 = _encodeConfirmation(proposalIdForAddingSupportedToken); + + const result = await multiSigWallet.submitTransaction( + mainTokenGovernor.address, + EMPTY_BYTES, + encodedConfirmation1, + 0, + {"from": accounts[0]} + ); + txIndex1 = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + }) + + it('Should confirm transaction 1 from accounts[0], the first signer and accounts[1], the second signer', async() => { + await multiSigWallet.confirmTransaction(txIndex1, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(txIndex1, {"from": accounts[1]}); + }); + + + it('Execute the multiSig confirmation of proposal 1 and wait 40 blocks', async() => { + await multiSigWallet.executeTransaction(txIndex1, {"from": accounts[0]}); + }); + + it('Queue the proposal and wait for minDelay', async() => { + + // Functions mainTokenGovernor.propose and mainTokenGovernor.queue have the same input, except for the + // description parameter, which we need to hash. + // + // A proposal can only be executed if the proposalId is the same as the one stored + // in the governer contract that has passed a vote. + // In the Governor.sol contract, the proposalId is created using all information used + // in to create the proposal: + // uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); + + const result = await mainTokenGovernor.queue( + [mainTokenGovernor.address], + [0], + [encoded_function_add_supporting_token], + description_hash, + {"from": accounts[0]} + ); + expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal("5"); + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + + var nextBlock = 1; + while (nextBlock <= 40) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + + }); + + it('Execute the proposal', async() => { + + const result = await mainTokenGovernor.execute( + [mainTokenGovernor.address], + [0], + [encoded_function_add_supporting_token], + description_hash, + {"from": accounts[0]} + ); + const successStatus = eventsHelper.getIndexedEventArgs(result, EXECUTE_TRANSACTION_EVENT)[1]; + expect(successStatus.toString()).to.equal(TRUE_EVENT_RETURN_IN_HEX) + }); + }) + + describe('#Relay the supported token to other address', () => { + /// ------------- Relay Token -------/// + it('Propose to relay Token to other address', async() => { + await streamReward1.transfer(mainTokenGovernor.address,web3.utils.toWei("1000000","ether"),{from: accounts[0]}); + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_ERC20_relay = web3.eth.abi.encodeFunctionCall({ + name: 'relayERC20', type: 'function', inputs: [{ type: 'address', - name: '_token' + name: 'target' + },{ + type: 'bytes', + name: 'data' }] - }, [streamReward1.address]); + }, [streamReward1.address, _encodeTransferFunction(accounts[0])]); // create a proposal in MainToken governor result = await mainTokenGovernor.propose( [mainTokenGovernor.address], [0], - [encoded_function_add_supporting_token], + [encoded_function_ERC20_relay], PROPOSAL_DESCRIPTION, {"from": STAKER_1} ); // retrieve the proposal id - proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + proposalIdForERC20elayFunction = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; }); it('Wait two blocks and then check that the proposal status is: Active', async() => { @@ -707,7 +917,7 @@ describe('Proposal flow', () => { nextBlock++; } // Check that the proposal is open for voting - expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal("1"); + expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal("1"); }); it('Vote on the proposal', async() => { @@ -729,7 +939,7 @@ describe('Proposal flow', () => { nextBlock++; } // Vote: - await mainTokenGovernor.castVote(proposalIdForAddingSupportedToken, "1", {"from": STAKER_1}); + await mainTokenGovernor.castVote(proposalIdForERC20elayFunction, "1", {"from": STAKER_1}); }); it('Wait 40 blocks and then check that the proposal status is: Succeeded', async() => { @@ -743,12 +953,11 @@ describe('Proposal flow', () => { nextBlock++; } - expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal("4"); + expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal(SUCCEEDED_PROPOSAL_STATE); }); - - it('Create multiSig transaction to confirm proposal for adding supported tokens', async() => { - encodedConfirmation1 = _encodeConfirmation(proposalIdForAddingSupportedToken); + it('Create multiSig transaction to confirm proposal for relaying supported tokens', async() => { + encodedConfirmation1 = _encodeConfirmation(proposalIdForERC20elayFunction); const result = await multiSigWallet.submitTransaction( mainTokenGovernor.address, @@ -770,7 +979,7 @@ describe('Proposal flow', () => { await multiSigWallet.executeTransaction(txIndex1, {"from": accounts[0]}); }); - it('Queue the proposal', async() => { + it('Queue the proposal and wait for minDelay', async() => { // Functions mainTokenGovernor.propose and mainTokenGovernor.queue have the same input, except for the // description parameter, which we need to hash. @@ -784,21 +993,21 @@ describe('Proposal flow', () => { const result = await mainTokenGovernor.queue( [mainTokenGovernor.address], [0], - [encoded_function_add_supporting_token], + [encoded_function_ERC20_relay], description_hash, {"from": accounts[0]} ); + expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal("5"); + const currentNumber = await web3.eth.getBlockNumber(); const block = await web3.eth.getBlock(currentNumber); - const timestamp = block.timestamp; - + const timestamp = block.timestamp; var nextBlock = 1; while (nextBlock <= 40) { await blockchain.mineBlock(timestamp + nextBlock); nextBlock++; } - expect((await mainTokenGovernor.state(proposalIdForAddingSupportedToken)).toString()).to.equal("5"); }); it('Execute the proposal', async() => { @@ -806,39 +1015,46 @@ describe('Proposal flow', () => { const result = await mainTokenGovernor.execute( [mainTokenGovernor.address], [0], - [encoded_function_add_supporting_token], + [encoded_function_ERC20_relay], description_hash, - {"from": accounts[0]} + {"from": accounts[0], + } ); + const successStatus = eventsHelper.getIndexedEventArgs(result, EXECUTE_TRANSACTION_EVENT)[1]; expect(successStatus.toString()).to.equal(TRUE_EVENT_RETURN_IN_HEX) + }); - /// ------------- Relay Token -------/// + }) + + describe('Relay Eth to an address', () => { it('Propose to add relay Token to other address', async() => { - await streamReward1.transfer(mainTokenGovernor.address,web3.utils.toWei("10000","ether"),{from: accounts[0]}); - const eightHours = 28800 - await blockchain.increaseTime(eightHours) - encoded_function_ERC20_relay = web3.eth.abi.encodeFunctionCall({ - name: 'relayERC20', + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_ETH_relay = web3.eth.abi.encodeFunctionCall({ + name: 'relayNativeToken', type: 'function', inputs: [{ type: 'address', name: 'target' + },{ + type: 'uint256', + name: 'value' },{ type: 'bytes', name: 'data' }] - }, [streamReward1.address, _encodeTransferFunction(accounts[0])]); + }, [accounts[0], EMPTY_BYTES, _encodeTransferFunction(accounts[0])]); // create a proposal in MainToken governor result = await mainTokenGovernor.propose( [mainTokenGovernor.address], [0], - [encoded_function_ERC20_relay], + [encoded_function_ETH_relay], PROPOSAL_DESCRIPTION, {"from": STAKER_1} ); // retrieve the proposal id - proposalIdForERC20elayFunction = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + proposalIdForETHRelayFunction = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; }); it('Wait two blocks and then check that the proposal status is: Active', async() => { @@ -853,7 +1069,7 @@ describe('Proposal flow', () => { nextBlock++; } // Check that the proposal is open for voting - expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal("1"); + expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal("1"); }); it('Vote on the proposal', async() => { @@ -875,7 +1091,7 @@ describe('Proposal flow', () => { nextBlock++; } // Vote: - await mainTokenGovernor.castVote(proposalIdForERC20elayFunction, "1", {"from": STAKER_1}); + await mainTokenGovernor.castVote(proposalIdForETHRelayFunction, "1", {"from": STAKER_1}); }); it('Wait 40 blocks and then check that the proposal status is: Succeeded', async() => { @@ -889,11 +1105,11 @@ describe('Proposal flow', () => { nextBlock++; } - expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal("4"); + expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal(SUCCEEDED_PROPOSAL_STATE); }); it('Create multiSig transaction to confirm proposal for relaying supported tokens', async() => { - encodedConfirmation1 = _encodeConfirmation(proposalIdForERC20elayFunction); + encodedConfirmation1 = _encodeConfirmation(proposalIdForETHRelayFunction); const result = await multiSigWallet.submitTransaction( mainTokenGovernor.address, @@ -915,7 +1131,7 @@ describe('Proposal flow', () => { await multiSigWallet.executeTransaction(txIndex1, {"from": accounts[0]}); }); - it('Queue the proposal', async() => { + it('Queue the proposal and wait for minDelay', async() => { // Functions mainTokenGovernor.propose and mainTokenGovernor.queue have the same input, except for the // description parameter, which we need to hash. @@ -929,10 +1145,12 @@ describe('Proposal flow', () => { const result = await mainTokenGovernor.queue( [mainTokenGovernor.address], [0], - [encoded_function_ERC20_relay], + [encoded_function_ETH_relay], description_hash, {"from": accounts[0]} ); + expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal("5"); + const currentNumber = await web3.eth.getBlockNumber(); const block = await web3.eth.getBlock(currentNumber); @@ -942,7 +1160,6 @@ describe('Proposal flow', () => { await blockchain.mineBlock(timestamp + nextBlock); nextBlock++; } - expect((await mainTokenGovernor.state(proposalIdForERC20elayFunction)).toString()).to.equal("5"); }); it('Execute the proposal', async() => { @@ -950,7 +1167,7 @@ describe('Proposal flow', () => { const result = await mainTokenGovernor.execute( [mainTokenGovernor.address], [0], - [encoded_function_ERC20_relay], + [encoded_function_ETH_relay], description_hash, {"from": accounts[0], } @@ -959,255 +1176,204 @@ describe('Proposal flow', () => { const successStatus = eventsHelper.getIndexedEventArgs(result, EXECUTE_TRANSACTION_EVENT)[1]; expect(successStatus.toString()).to.equal(TRUE_EVENT_RETURN_IN_HEX) }); + }) - //relay ETH - - /// ------------- Relay Token -------/// - it('Propose to add relay Token to other address', async() => { - const eightHours = 28800 - await blockchain.increaseTime(eightHours) - encoded_function_ETH_relay = web3.eth.abi.encodeFunctionCall({ - name: 'relayNativeToken', - type: 'function', - inputs: [{ - type: 'address', - name: 'target' - },{ - type: 'uint256', - name: 'value' - },{ - type: 'bytes', - name: 'data' - }] - }, [accounts[0], EMPTY_BYTES, _encodeTransferFunction(accounts[0])]); - // create a proposal in MainToken governor - result = await mainTokenGovernor.propose( - [mainTokenGovernor.address], - [0], - [encoded_function_ETH_relay], - PROPOSAL_DESCRIPTION, - {"from": STAKER_1} - ); - // retrieve the proposal id - proposalIdForETHRelayFunction = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; - }); - - it('Wait two blocks and then check that the proposal status is: Active', async() => { - - const currentNumber = await web3.eth.getBlockNumber(); - const block = await web3.eth.getBlock(currentNumber); - const timestamp = block.timestamp; - - var nextBlock = 1; - while (nextBlock <= 2) { - await blockchain.mineBlock(timestamp + nextBlock); - nextBlock++; - } - // Check that the proposal is open for voting - expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal("1"); - }); - - it('Vote on the proposal', async() => { - - // enum VoteType { - // Against, - // For, - // Abstain - // } - // => 0 = Against, 1 = For, 2 = Abstain - - const currentNumber = await web3.eth.getBlockNumber(); - const block = await web3.eth.getBlock(currentNumber); - const timestamp = block.timestamp; - - var nextBlock = 1; - while (nextBlock <= 2) { - await blockchain.mineBlock(timestamp + nextBlock); - nextBlock++; - } - // Vote: - await mainTokenGovernor.castVote(proposalIdForETHRelayFunction, "1", {"from": STAKER_1}); - }); - - it('Wait 40 blocks and then check that the proposal status is: Succeeded', async() => { - const currentNumber = await web3.eth.getBlockNumber(); - const block = await web3.eth.getBlock(currentNumber); - const timestamp = block.timestamp; - - var nextBlock = 1; - while (nextBlock <= 40) { - await blockchain.mineBlock(timestamp + nextBlock); - nextBlock++; - } - - expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal("4"); - }); - - it('Create multiSig transaction to confirm proposal for relaying supported tokens', async() => { - encodedConfirmation1 = _encodeConfirmation(proposalIdForETHRelayFunction); - - const result = await multiSigWallet.submitTransaction( - mainTokenGovernor.address, - EMPTY_BYTES, - encodedConfirmation1, - 0, - {"from": accounts[0]} - ); - txIndex1 = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; - }) - - it('Should confirm transaction 1 from accounts[0], the first signer and accounts[1], the second signer', async() => { - await multiSigWallet.confirmTransaction(txIndex1, {"from": accounts[0]}); - await multiSigWallet.confirmTransaction(txIndex1, {"from": accounts[1]}); - }); - - - it('Execute the multiSig confirmation of proposal 1 and wait 40 blocks', async() => { - await multiSigWallet.executeTransaction(txIndex1, {"from": accounts[0]}); - }); - - it('Queue the proposal', async() => { - - // Functions mainTokenGovernor.propose and mainTokenGovernor.queue have the same input, except for the - // description parameter, which we need to hash. - // - // A proposal can only be executed if the proposalId is the same as the one stored - // in the governer contract that has passed a vote. - // In the Governor.sol contract, the proposalId is created using all information used - // in to create the proposal: - // uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); - - const result = await mainTokenGovernor.queue( - [mainTokenGovernor.address], - [0], - [encoded_function_ETH_relay], - description_hash, - {"from": accounts[0]} - ); - const currentNumber = await web3.eth.getBlockNumber(); - const block = await web3.eth.getBlock(currentNumber); - - const timestamp = block.timestamp; - var nextBlock = 1; - while (nextBlock <= 40) { - await blockchain.mineBlock(timestamp + nextBlock); - nextBlock++; - } - expect((await mainTokenGovernor.state(proposalIdForETHRelayFunction)).toString()).to.equal("5"); - }); - - it('Execute the proposal', async() => { - - const result = await mainTokenGovernor.execute( - [mainTokenGovernor.address], - [0], - [encoded_function_ETH_relay], - description_hash, - {"from": accounts[0], - } - ); - - const successStatus = eventsHelper.getIndexedEventArgs(result, EXECUTE_TRANSACTION_EVENT)[1]; - expect(successStatus.toString()).to.equal(TRUE_EVENT_RETURN_IN_HEX) - }); + describe('#TestCases for vote balance check and black list check', () => { - it('Should not have enough vote balance to propose', async() => { - const eightHours = 28800 - await blockchain.increaseTime(eightHours) - encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ - name: 'addSupportingToken', - type: 'function', - inputs: [{ - type: 'address', - name: '_token' - }] - }, [streamReward1.address]); - // create a proposal in MainToken governor + const _transferFromMultiSigTreasury = async (_account) => { + const result = await multiSigWallet.submitTransaction( + FTHMToken.address, + EMPTY_BYTES, + _encodeTransferFunction(_account), + 0, + {"from": accounts[0]} + ); + txIndex4 = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; - let errorMessage = "Governor: proposer votes below threshold"; - await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) - await _transferFromMultiSigTreasury(NOT_STAKER) - await stakingService.createLock(web3.utils.toWei('1','ether'),lockingPeriod,{from: NOT_STAKER}); - await shouldRevert( - mainTokenGovernor.propose( - [stakingService.address], - [0], - [encoded_function], - "PROPOSAL_DESCRIPTION", - {"from": NOT_STAKER} - ), - errTypes.revert, - errorMessage - ); - - // retrieve the proposal id - proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; - }); + await multiSigWallet.confirmTransaction(txIndex4, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(txIndex4, {"from": accounts[1]}); - it('Should not have enough vote balance to propose', async() => { - const eightHours = 28800 - await blockchain.increaseTime(eightHours) - encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ - name: 'addSupportingToken', - type: 'function', - inputs: [{ - type: 'address', - name: '_token' - }] - }, [streamReward1.address]); - // create a proposal in MainToken governor + await multiSigWallet.executeTransaction(txIndex4, {"from": accounts[1]}); + } - await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) - await _transferFromMultiSigTreasury(NOT_STAKER) - await stakingService.createLock(T_TO_STAKE,lockingPeriod,{from: NOT_STAKER}); - await mainTokenGovernor.propose( - [stakingService.address], - [0], - [encoded_function], - "PROPOSAL_DESCRIPTION", - {"from": NOT_STAKER} - ), - - // retrieve the proposal id - proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; - }); - + it('Should not have enough vote balance to propose', async() => { + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ + name: 'addSupportingToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [streamReward1.address]); + // create a proposal in MainToken governor - it('Should blacklist a proposer', async() =>{ - const _blacklistAProposer = async(account, blacklistStatus) => { - const result = await multiSigWallet.submitTransaction( - mainTokenGovernor.address, - EMPTY_BYTES, - _encodeBlacklistProposer(account, blacklistStatus), - 0, - {"from": accounts[0]} - ) - - const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); - await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); - await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); - } - - await _blacklistAProposer(accounts[5], true) - }) + let errorMessage = "Governor: proposer votes below threshold"; + await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) + await _transferFromMultiSigTreasury(NOT_STAKER) + //get 1VOTE Token only. + await stakingService.createLock(web3.utils.toWei('999','ether'),lockingPeriod,{from: NOT_STAKER}); + await shouldRevert( + mainTokenGovernor.propose( + [stakingService.address], + [0], + [encoded_function], + PROPOSAL_DESCRIPTION, + {"from": NOT_STAKER} + ), + errTypes.revert, + errorMessage + ); + + // retrieve the proposal id + proposalIdToFailWithoutEnoughVotes = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + it('Proposal created to relay ETH to accounts[1] which should fail without enough vote threshold reached', async() => { + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_ETH_relay = web3.eth.abi.encodeFunctionCall({ + name: 'relayNativeToken', + type: 'function', + inputs: [{ + type: 'address', + name: 'target' + },{ + type: 'uint256', + name: 'value' + },{ + type: 'bytes', + name: 'data' + }] + }, [accounts[1], EMPTY_BYTES, _encodeTransferFunction(accounts[0])]); + // create a proposal in MainToken governor + result = await mainTokenGovernor.propose( + [mainTokenGovernor.address], + [0], + [encoded_function_ETH_relay], + PROPOSAL_DESCRIPTION, + {"from": STAKER_1} + ); + // retrieve the proposal id + proposalIdToFailWithoutEnoughVotes = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + it('Wait two blocks and then check that the proposal status is: Active', async() => { + + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 2) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + // Check that the proposal is open for voting + expect((await mainTokenGovernor.state(proposalIdToFailWithoutEnoughVotes)).toString()).to.equal("1"); + }); + + it('Vote on the proposal', async() => { + + // enum VoteType { + // Against, + // For, + // Abstain + // } + // => 0 = Against, 1 = For, 2 = Abstain + + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 2) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + // Vote: + await mainTokenGovernor.castVote(proposalIdToFailWithoutEnoughVotes, "1", {"from": NOT_STAKER}); + }); + + it('Wait 40 blocks and then check that the proposal status is: DEFEATED as not enough votes were cast', async() => { + const currentNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(currentNumber); + const timestamp = block.timestamp; + + var nextBlock = 1; + while (nextBlock <= 40) { + await blockchain.mineBlock(timestamp + nextBlock); + nextBlock++; + } + + expect((await mainTokenGovernor.state(proposalIdToFailWithoutEnoughVotes)).toString()).to.equal(DEFEATED_PROPOSAL_STATE); + }); + + it('Should have enough vote balance to propose after creating lock for NOT_STAKER', async() => { + const proposalTimeDelay = 5 + await blockchain.increaseTime(proposalTimeDelay) + encoded_function_add_supporting_token = web3.eth.abi.encodeFunctionCall({ + name: 'addSupportingToken', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [streamReward1.address]); + // create a proposal in MainToken governor + + await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) + await _transferFromMultiSigTreasury(NOT_STAKER) + await stakingService.createLock(T_TO_STAKE,lockingPeriod,{from: NOT_STAKER}); + await mainTokenGovernor.propose( + [stakingService.address], + [0], + [encoded_function], + "PROPOSAL_DESCRIPTION", + {"from": NOT_STAKER} + ), - it('Should revert on propose by blacklisted msg.sender', async() =>{ - let errorMessage = "Proposer is blacklisted"; + // retrieve the proposal id + proposalIdForAddingSupportedToken = eventsHelper.getIndexedEventArgs(result, PROPOSAL_CREATED_EVENT)[0]; + }); + + + it('Should blacklist a proposer', async() =>{ + const _blacklistAProposer = async(account, blacklistStatus) => { + const result = await multiSigWallet.submitTransaction( + mainTokenGovernor.address, + EMPTY_BYTES, + _encodeBlacklistProposer(account, blacklistStatus), + 0, + {"from": accounts[0]} + ) - await shouldRevert( - mainTokenGovernor.propose( - [box.address], - [0], - [encoded_function], - PROPOSAL_DESCRIPTION, - {"from": accounts[5]} - ), - errTypes.revert, - errorMessage - ); - }) - }); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _blacklistAProposer(accounts[5], true) + }) + + it('Should revert on propose by blacklisted msg.sender', async() =>{ + let errorMessage = "Proposer is blacklisted"; + await shouldRevert( + mainTokenGovernor.propose( + [box.address], + [0], + [encoded_function], + PROPOSAL_DESCRIPTION, + {"from": accounts[5]} + ), + errTypes.revert, + errorMessage + ); + }) + }) + describe("Emergency Stop through multisig", async() => { it('Emergency stop the governor', async() =>{ diff --git a/scripts/tests/dao/staking/staking.test.js b/scripts/tests/dao/staking/staking.test.js index c9b743a..331e54d 100644 --- a/scripts/tests/dao/staking/staking.test.js +++ b/scripts/tests/dao/staking/staking.test.js @@ -1394,7 +1394,7 @@ describe("Staking Test", () => { }) - it('Should set Max Lock Positions', async() => { + it('Should not set Max Lock Positions', async() => { const _setMaxLockPositionsBad = async( newMaxLockPositions From bc11058766c36a46ba48fb6589f7f87b7a9d21ee Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 8 Mar 2023 15:22:19 +0545 Subject: [PATCH 23/56] added comments and documenting scenarios --- contracts/dao/governance/Governor.sol | 26 +++++++++++--- .../dao/governance/MainTokenGovernor.sol | 24 ++++++++++--- .../extensions/GovernorSettings.sol | 9 +++++ .../extensions/GovernorTimelockControl.sol | 3 ++ .../dao/staking/interfaces/IStakingGetter.sol | 36 ++++++------------- .../dao/staking/packages/StakingGetters.sol | 2 ++ .../dao/staking/packages/StakingHandler.sol | 36 ++++++++++++++++++- docs/SCENARIOS-INSTRUCTIONS.md | 4 +-- 8 files changed, 103 insertions(+), 37 deletions(-) diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index a2a2491..d0eba9b 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -227,7 +227,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { calldatas, snapshot, deadline, description); return proposalId; } - + /** + * @dev Only Multisig is able to confirm a proposal + */ function confirmProposal(uint256 _proposalId) public onlyMultiSig notExecuted(_proposalId) notConfirmed(_proposalId) { _requireNotExpired(_proposalId); isConfirmed[_proposalId] = true; @@ -235,37 +237,51 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { require(status == ProposalState.Succeeded || status == ProposalState.Queued, "proposal not successful"); emit ConfirmProposal(msg.sender, _proposalId); } - + /** + * @dev Only Multisig is able to revoke a proposal confirmation + */ function revokeConfirmation(uint256 _proposalId) public onlyMultiSig notExecuted(_proposalId) { _requireConfirmed(_proposalId); isConfirmed[_proposalId] = false; emit RevokeConfirmation(msg.sender, _proposalId); } + /** + * @dev Only Multisig can update + */ function updateMultiSig(address newMultiSig) public onlyMultiSig { require(newMultiSig != address(0), "zero address"); emit MultiSigUpdated(newMultiSig, multiSig); multiSig = newMultiSig; } - + /** + * @dev Only Multisig can update + */ function updateMaxTargets(uint256 newMaxTargets) public onlyMultiSig { require(newMaxTargets != 0, "zero value"); emit MaxTargetUpdated(newMaxTargets, maxTargets); maxTargets = newMaxTargets; } + /** + * @dev Only Multisig can update + */ function updateProposalTimeDelay(uint256 newProposalTimeDelay) public onlyMultiSig { require(newProposalTimeDelay != 0, "zero value"); emit ProposalTimeDelayUpdated(newProposalTimeDelay, proposalTimeDelay); proposalTimeDelay = newProposalTimeDelay; } - + /** + * @dev Only Multisig can update + */ function updateProposalLifetime(uint256 newProposalLifetime) public onlyMultiSig { require(newProposalLifetime>= MINIMUM_LIFETIME, "less than minimum"); emit ProposalLifetimeUpdated(newProposalLifetime, newProposalLifetime); proposalLifetime = newProposalLifetime; } - + /** + * @dev Only Multisig can blacklist a an account or unblacklist an account + */ function setBlacklistStatusForProposer(address account, bool blacklistStatus) public onlyMultiSig { isBlacklisted[account] = blacklistStatus; } diff --git a/contracts/dao/governance/MainTokenGovernor.sol b/contracts/dao/governance/MainTokenGovernor.sol index 1b380c7..f414653 100644 --- a/contracts/dao/governance/MainTokenGovernor.sol +++ b/contracts/dao/governance/MainTokenGovernor.sol @@ -49,7 +49,9 @@ contract MainTokenGovernor is ) public override(Governor, IGovernor) returns (uint256) { return super.propose(targets, values, calldatas, description); } - + /** + * @dev Cancelling of proposal can be done only through Multisig + */ function cancelProposal( address[] memory targets, uint256[] memory values, @@ -83,6 +85,10 @@ contract MainTokenGovernor is return super.state(proposalId); } + /** + * @dev A multisig can stop this contract. Once stopped we will have to migrate. + * Once this function is called, the contract cannot be made live again. + */ function emergencyStop() public onlyMultiSig{ _emergencyStop(); for(uint i = 0; i < listOfSupportedTokens.length;i++){ @@ -97,10 +103,20 @@ contract MainTokenGovernor is require(sent, "Failed to send ether"); } } - + /** + * @dev Adds supporting tokens so that if there are tokens then it can be transferred + * Only Governance is able to access this function. + * It has to go through proposal and successful voting for execution. + */ function addSupportingToken(address _token) public onlyGovernance { _addSupportedToken(_token); } + + /** + * @dev Removes supporting tokens + * Only Governance is able to access this function. + * It has to go through proposal and successful voting for execution. + */ function removeSupportingToken(address _token) public onlyGovernance { _removeSupportingToken(_token); } @@ -127,7 +143,7 @@ contract MainTokenGovernor is /** * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor * is some contract other than the governor itself, like when using a timelock, this function can be invoked - * in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. + * in a governance proposal to recover tokens that was sent to the governor contract by mistake. * Note that if the executor is simply the governor itself, use of `relay` is redundant. */ function relayERC20( @@ -142,7 +158,7 @@ contract MainTokenGovernor is /** * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor * is some contract other than the governor itself, like when using a timelock, this function can be invoked - * in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. + * in a governance proposal to recover Ether that was sent to the governor contract by mistake. * Note that if the executor is simply the governor itself, use of `relay` is redundant. */ function relayNativeToken( diff --git a/contracts/dao/governance/extensions/GovernorSettings.sol b/contracts/dao/governance/extensions/GovernorSettings.sol index 3d1bb67..3fa9d15 100644 --- a/contracts/dao/governance/extensions/GovernorSettings.sol +++ b/contracts/dao/governance/extensions/GovernorSettings.sol @@ -25,14 +25,23 @@ abstract contract GovernorSettings is Governor { _setProposalThreshold(initialProposalThreshold); } + /** + * @dev Has to go through proposals and successful voting to update by Governance + */ function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance { _setVotingDelay(newVotingDelay); } + /** + * @dev Has to go through proposals and successful voting to update by Governance + */ function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance { _setVotingPeriod(newVotingPeriod); } + /** + * @dev Has to go through proposals and successful voting to update by Governance + */ function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance { _setProposalThreshold(newProposalThreshold); } diff --git a/contracts/dao/governance/extensions/GovernorTimelockControl.sol b/contracts/dao/governance/extensions/GovernorTimelockControl.sol index 834750d..c9319e2 100644 --- a/contracts/dao/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/dao/governance/extensions/GovernorTimelockControl.sol @@ -24,6 +24,9 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { _updateTimelock(newTimelock); } + /** + * @notice The proposal must be confirmed by multisig before it can be queued + */ function queue( address[] memory targets, uint256[] memory values, diff --git a/contracts/dao/staking/interfaces/IStakingGetter.sol b/contracts/dao/staking/interfaces/IStakingGetter.sol index 62a4507..4be004b 100644 --- a/contracts/dao/staking/interfaces/IStakingGetter.sol +++ b/contracts/dao/staking/interfaces/IStakingGetter.sol @@ -6,21 +6,6 @@ pragma solidity 0.8.16; import "../StakingStructs.sol"; interface IStakingGetter { - // function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256); - - // function getStream(uint256 streamId) - // external - // view - // returns ( - // address streamOwner, - // address rewardToken, - // uint256 rewardDepositAmount, - // uint256 rewardClaimedAmount, - // uint256 maxDepositAmount, - // uint256 rps, - // uint256 tau, - // StreamStatus status - // ); function getAllLocks(address account) external view returns (LockedBalance[] memory); function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256); @@ -29,15 +14,16 @@ interface IStakingGetter { address account, uint256 lockId ) external view returns (uint256); - //function readBySlot(uint256 slot) external view returns(bytes32); function getStreamSchedule(uint256 streamId) external view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards); - - // function getStreamsCount() external view returns (uint256); - - // function getLatestRewardsPerShare(uint256 streamId) external view returns (uint256); - - // function getWeight() external view returns (Weight memory); - // function getStreamStatus(uint256 streamId) external view returns ( - // StreamStatus status - // ); + function getStream( + uint256 streamId + ) + external + view + returns ( + uint256 rewardDepositAmount, + uint256 rewardClaimedAmount, + uint256 rps, + StreamStatus status + ); } diff --git a/contracts/dao/staking/packages/StakingGetters.sol b/contracts/dao/staking/packages/StakingGetters.sol index 46011c4..1a648e7 100644 --- a/contracts/dao/staking/packages/StakingGetters.sol +++ b/contracts/dao/staking/packages/StakingGetters.sol @@ -39,6 +39,7 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { uint256 streamId ) external + override view returns ( uint256 rewardDepositAmount, @@ -55,4 +56,5 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { stream.status ); } + } diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 7daea0a..0b52f2a 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -62,6 +62,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A minLockPeriod = _minLockPeriod; } + /** + * @dev The function is callable only once, and it can be done only with admin, + * which at initial setup is Multisig + */ function initializeMainStream( address _owner, uint256[] memory scheduleTimes, @@ -144,7 +148,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A ); emit StreamProposed(streamId, streamOwner, rewardToken, maxDepositAmount); } - + /** + * @dev This function creates a stream and makes it live. Only the Stream Owner is able to call this function. + * Stream Owner is set while proposing a stream + */ function createStream(uint256 streamId, uint256 rewardTokenAmount) public override pausable(1){ Stream storage stream = streams[streamId]; require(stream.status == StreamStatus.PROPOSED, "nt proposed"); @@ -167,6 +174,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A _transfer(rewardTokenAmount,stream.rewardToken); } + /** + * @dev Proposed stream can be cancelled by Stream Manager, which at the time of deployment is Multisig + */ function cancelStreamProposal(uint256 streamId) public override onlyRole(STREAM_MANAGER_ROLE) { Stream storage stream = streams[streamId]; require(stream.status == StreamStatus.PROPOSED, "nt proposed"); @@ -175,6 +185,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A emit StreamProposalCancelled(streamId, stream.owner, stream.rewardToken); } + /** + * @dev A stream can be removed after all the rewards pending have been withdrawn. + * Stream can be removed by the Stream Manager which is Multisig as time of deployment. + */ function removeStream(uint256 streamId, address streamFundReceiver) public override onlyRole(STREAM_MANAGER_ROLE) { if(streamId == 0){ revert StreamIdZero(); @@ -196,6 +210,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A emit StreamRemoved(streamId, stream.owner, stream.rewardToken); } + /** + * @dev Creating locks for council can be done by Admin only which is Multisig. + * Multisig can create locks for councils + */ function createLocksForCouncils(CreateLockParams[] calldata lockParams) public override onlyRole(DEFAULT_ADMIN_ROLE) { if(councilsInitialized == true){ revert AlreadyInitialized(); @@ -296,6 +314,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A _withdraw(MAIN_STREAM); } + /** + * @dev Vault can be updated only if the contract is at paused state. + * Only Admin that is, Multisig at the time of deployment can update Vault + */ function updateVault(address _vault) public override onlyRole(DEFAULT_ADMIN_ROLE) { // enforce pausing this contract before updating the address. // This mitigates the risk of future invalid reward claims @@ -314,6 +336,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A vault = _vault; } + /** + * @dev Penalty accrued due to early unlocking can be withdrawn to some address, most likely the treasury. + * Address with TREASURY_ROLE can access this function, which is Multisig at time of deployment + */ function withdrawPenalty(address penaltyReceiver) public override pausable(1) onlyRole(TREASURY_ROLE) { if(totalPenaltyBalance == 0){ revert ZeroPenalty(); @@ -356,6 +382,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IVault(vault).deposit(_token, _amount); } + /** + * @dev This allows for setting up minimum locking period. + * Only admin which is Multisig at deployment can call this + */ function setMinimumLockPeriod(uint256 _minLockPeriod) public override onlyRole(DEFAULT_ADMIN_ROLE){ if(_minLockPeriod > maxLockPeriod){ revert MaxLockPeriodExceeded(); @@ -363,6 +393,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A minLockPeriod = _minLockPeriod; } + /** + * @dev This allows for setting up maximum lock positions. + * Only admin which is Multisig at deployment can call this + */ function setMaxLockPositions(uint256 newMaxLockPositions) public override onlyRole(DEFAULT_ADMIN_ROLE){ if(newMaxLockPositions < maxLockPositions){ revert BadMaxLockPositions(); diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index c2967c9..6e287c1 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -21,12 +21,12 @@ Hardcode: **How to create dex pool with Native Tokenr** 1. In scripts/units/create_pool_dex_xdc.js Hardcode the followings: -* TOKEN_ADDRESS +* TOKEN_ADDRESS: The token address to create a pair with XDC * AMOUNT_TOKEN_DESIRED * AMOUNT_TOKEN_MIN * AMOUNT_ETH_MIN * DEX_ROUTER_ADDRESS //this comes from external address -* TOKEN_ETH +* TOKEN_ETH: The amount of ETH you are willing to spend 2. coralX scenario --run createDexXDCPoolApothem From c974c50e4db555400bd744b30c1092eba154d073 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 10 Mar 2023 15:25:20 +0545 Subject: [PATCH 24/56] refactoring scripts to store txn indexes --- config/newly-generated-transaction-index.json | 9 +- .../save-address/1_save_address_deployment.js | 2 +- .../save-address/2_save_address_setup.js | 2 +- .../save-address/3_save_address_prod.js | 2 +- scripts/units/create-upgrade.js | 19 +--- scripts/units/create_pool_dex.js | 12 +-- scripts/units/create_pool_dex_xdc.js | 23 ++-- .../units/create_stablecoin_open_position.js | 18 +--- .../units/create_stablecoin_proxy_wallet.js | 12 +-- scripts/units/create_stream.js | 15 +-- scripts/units/execute-proposals.js | 17 ++- scripts/units/helpers/transactionSaver.js | 101 ++++++++++++++++++ scripts/units/propose-proposal.js | 15 +-- scripts/units/propose_stream.js | 15 +-- scripts/units/queue-proposal.js | 18 ++-- scripts/units/setup-multisig-owners.js | 3 + scripts/units/setup_council_stakes.js | 4 + .../units/stableswap-daily-limit-update.js | 14 +-- scripts/units/stableswap-setup.js | 22 ++-- scripts/units/transfer-tokens.js | 16 ++- 20 files changed, 182 insertions(+), 157 deletions(-) create mode 100644 scripts/units/helpers/transactionSaver.js diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index b960057..d923aaa 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -1 +1,8 @@ -{"addLiquidityTxnIdx":"0x000000000000000000000000000000000000000000000000000000000000004a"} \ No newline at end of file +{ + "txIndexCreateLock": [ + { + "id": 1, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000005" + } + ] +} \ No newline at end of file diff --git a/scripts/migrations/save-address/1_save_address_deployment.js b/scripts/migrations/save-address/1_save_address_deployment.js index ada3e27..ab07b26 100644 --- a/scripts/migrations/save-address/1_save_address_deployment.js +++ b/scripts/migrations/save-address/1_save_address_deployment.js @@ -19,7 +19,7 @@ module.exports = async function(deployer) { rewardsCalculator: RewardsCalculator.address, } - let data = JSON.stringify(addresses); + let data = JSON.stringify(addresses, null, " "); fs.writeFileSync('./addresses.json',data, function(err){ if(err){ console.log(err) diff --git a/scripts/migrations/save-address/2_save_address_setup.js b/scripts/migrations/save-address/2_save_address_setup.js index 8ceefca..f16a3eb 100644 --- a/scripts/migrations/save-address/2_save_address_setup.js +++ b/scripts/migrations/save-address/2_save_address_setup.js @@ -15,7 +15,7 @@ module.exports = async function(deployer) { } - let data = JSON.stringify(newAddresses); + let data = JSON.stringify(newAddresses, null, " "); fs.writeFileSync('./addresses.json',data, function(err){ if(err){ console.log(err) diff --git a/scripts/migrations/save-address/3_save_address_prod.js b/scripts/migrations/save-address/3_save_address_prod.js index 4784a14..3b6475e 100644 --- a/scripts/migrations/save-address/3_save_address_prod.js +++ b/scripts/migrations/save-address/3_save_address_prod.js @@ -17,7 +17,7 @@ module.exports = async function(deployer) { } - let data = JSON.stringify(newAddresses); + let data = JSON.stringify(newAddresses, null, " "); fs.writeFileSync('./addresses.json',data, function(err){ if(err){ console.log(err) diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index 6f6bede..ae1f401 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -1,5 +1,6 @@ const fs = require('fs'); const constants = require('./helpers/constants') +const txnHelper = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); @@ -8,9 +9,9 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); //RIGHT NOW SETUP FOR STAKING -const PROXY_ADMIN = "0x43d97AD756fe2b7E48a2384eD7c400Db37698167" -const PROXY = "0x06F32926169b922F5e885c8a31CB7e60D554A6E6" -const IMPLEMENTATION_ADDRESS = "0xe017a18Ad42abAE2e53F9A70EF037Ce52e2Eb484" +const PROXY_ADMIN = "0xB7a8f3A8178B21499b56d9d054119821953d2C3f" +const PROXY = "0xFD21E72b63568942E541284D275ce1057e7F1257" +const IMPLEMENTATION_ADDRESS = "0xa5B675dd61c00C41F3FA5b919b7E917A61dbE7f7" const _encodeUpgradeFunction = (_proxy, _impl) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'upgrade', @@ -47,17 +48,7 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - let upgradeTxn = { - upgradeTxnIdx: tx - } - - let data = JSON.stringify(upgradeTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("upgradeTxn",tx) } await _upgrade( diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 9490c2f..74cda82 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -1,5 +1,6 @@ const fs = require('fs'); const constants = require('./helpers/constants') +const txnHelper = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); @@ -149,16 +150,7 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); - let addLiquidityTxn = { - addLiquidityTxnIdx: txIndexAddLiquidity - } - let data = JSON.stringify(addLiquidityTxn); - - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("createPoolWithTokenPair", txIndexAddLiquidity) } diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 6b97074..7dfe007 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -1,6 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -8,14 +9,14 @@ const addresses = JSON.parse(rawdata); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); -const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address -const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('5', 'ether') -const AMOUNT_TOKEN_MIN = web3.utils.toWei('3', 'ether') -const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') +const TOKEN_ADDRESS = "0x746a59A8F41DdC954542B6697954a94868126885" //FTHM address +const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') +const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') +const AMOUNT_ETH_MIN = web3.utils.toWei('0', 'ether') //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS -const TOKEN_ETH = web3.utils.toWei('10', 'ether') +const TOKEN_ETH = web3.utils.toWei('3', 'ether') const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -111,17 +112,7 @@ module.exports = async function(deployer) { let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); - - let addLiquidityTxn = { - addLiquidityTxnIdx: txIndexAddLiquidity - } - let data = JSON.stringify(addLiquidityTxn); - - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("createPoolWithXDC", txIndexAddLiquidity) } diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 03ccade..c2431f2 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -1,6 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -21,11 +22,6 @@ const xdcAdapter = addressesExternal.xdcAdapter const stablecoinAdapter = addressesExternal.stablecoinAdapter const collateralPoolId = addressesExternal.collateralPoolId -// const positionMananger = "0xe485eDc3D5aba4dbEcD76a78e6c71c8F5E114F3b" -// const stabilityFeeCollector = "0x62889248B6C81D31D7acc450cc0334D0AA58A14A" -// const xdcAdapter = "0xc3c7f26ffD1cd5ec682E23C076471194DE8ce4f1" -// const stablecoinAdapter = "0x07a2C89774a3F3c57980AD7A528Aea6F262d8939" -// const collateralPoolId = '0x5844430000000000000000000000000000000000000000000000000000000000' const stablecoinAmount = web3.utils.toWei('5') const data = "0x00" @@ -113,15 +109,5 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(txExecute, {gas: 8000000}); await multiSigWallet.executeTransaction(txExecute, {gas: 8000000}); - let openPositionTxn = { - openPositionTxnIdx: txExecute - } - let data = JSON.stringify(openPositionTxn); - - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) - + await txnHelper.saveTxnIndex("openPositionTransaction", txExecute) } \ No newline at end of file diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index 1832ac8..2e7ea0b 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -1,6 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -50,14 +51,5 @@ module.exports = async function(deployer) { } }) - let proxyWalletTxn = { - proxyWalletTxnIdx: txIndexBuild - } - let data2 = JSON.stringify(proxyWalletTxn); - - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data2, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("proxyWalletTxn", txIndexBuild) } \ No newline at end of file diff --git a/scripts/units/create_stream.js b/scripts/units/create_stream.js index 53ac787..1ccc63b 100644 --- a/scripts/units/create_stream.js +++ b/scripts/units/create_stream.js @@ -1,5 +1,6 @@ const fs = require('fs'); const constants = require('./helpers/constants') +const txnHelper = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); @@ -99,18 +100,8 @@ module.exports = async function(deployer) { STREAM_ID, REWARD_PROPOSAL_AMOUNT ) - - let streamTxn = { - approveStreamRewardsTxnIdx: approveStreamRewardsTxnIdx, - createStreamRewardsTxnIdx: createStreamRewardsTxnIdx - } - let data = JSON.stringify(streamTxn); - - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("approveStreamRewardsTxn", approveStreamRewardsTxnIdx) + await txnHelper.saveTxnIndex("createStreamRewardsTxn", createStreamRewardsTxnIdx) } diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index adcb662..4c06558 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -1,6 +1,8 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -63,7 +65,9 @@ module.exports = async function(deployer) { // Calldatas: The function you want to call should be encoded // Description - const DESCRIPTION_HASH = '' + const DESCRIPTION_HASH_STRING = ''//note bytes32 + const DESCRIPTION_HASH = web3.utils.keccak256(DESCRIPTION_HASH_STRING) + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const encodedFunctionToCall = _encodedFucntionToCall() @@ -88,16 +92,7 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - let executeProposalTxn = { - executeProposalTxnIdx: tx - } - - let data = JSON.stringify(executeProposalTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex("executeProposalTxn",tx) } await _executeProposal( diff --git a/scripts/units/helpers/transactionSaver.js b/scripts/units/helpers/transactionSaver.js new file mode 100644 index 0000000..af563b0 --- /dev/null +++ b/scripts/units/helpers/transactionSaver.js @@ -0,0 +1,101 @@ +const fs = require('fs'); + +const rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); +const constants = require('./constants') + +async function saveTxnIndex( + TransactionName, + txnIndex +) +{ + let newTxnStore; + + if(rawdata.length <=0){ + //if no data present just create a new object and have idx as 1 + let object = {} + object[TransactionName] = + [ + { + "id":1, + "txnIndex": txnIndex + } + ] + newTxnStore = object + }else{ + let object = JSON.parse(rawdata) + if(object.hasOwnProperty(TransactionName)){ + //if key is already there push to it + object[TransactionName].push( + { + "id": object[TransactionName].length+1, + "txnIndex": txnIndex + } + ) + }else{ + //if key is not present make a new object + object[TransactionName] = [ + { + "id":1, + "txnIndex": txnIndex + } + ] + } + newTxnStore = object + } + + let data = JSON.stringify(newTxnStore,null," "); + + fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ + if(err){ + console.log(err) + } + }) + +} + +module.exports = { + saveTxnIndex +} + +//The above code will give output like below: + +// { +// "transferIndex": [ +// { +// "id": 1, +// "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000000e" +// }, +// { +// "id": 2, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000013" +// }, +// { +// "id": 3, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000017" +// }, +// { +// "id": 4, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000018" +// } +// ], +// "addLiquidityXDCTxnIdx": [ +// { +// "id": 1, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000010" +// }, +// { +// "id": 2, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000012" +// }, +// { +// "id": 3, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000016" +// } +// ], +// "upgradeTxnIdx": [ +// { +// "id": 1, +// "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000014" +// } +// ] +// } \ No newline at end of file diff --git a/scripts/units/propose-proposal.js b/scripts/units/propose-proposal.js index 07d8a0b..6972d09 100644 --- a/scripts/units/propose-proposal.js +++ b/scripts/units/propose-proposal.js @@ -1,4 +1,6 @@ const fs = require('fs'); +const txnHelper = require('./helpers/transactionSaver') +const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); @@ -85,16 +87,9 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - let proposeProposalTxn = { - proposeProposalTxnIdx: tx - } - - let data = JSON.stringify(proposeProposalTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + await txnHelper.saveTxnIndex( + "proposeProposalTxn",tx + ) } await _proposeProposal( diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 3d07d97..2530afa 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -1,6 +1,8 @@ const fs = require('fs'); +const txnHelper = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -110,17 +112,8 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - let proposeStreamTxn = { - proposeStreamTxnIdx: tx - } - - let data = JSON.stringify(proposeStreamTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + + await txnHelper.saveTxnIndex("proposeStreamTxn",tx) } await _proposeStreamFromMultiSig( diff --git a/scripts/units/queue-proposal.js b/scripts/units/queue-proposal.js index b63c408..a4dd31e 100644 --- a/scripts/units/queue-proposal.js +++ b/scripts/units/queue-proposal.js @@ -1,6 +1,8 @@ const fs = require('fs'); +const txnHelper = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -60,7 +62,8 @@ module.exports = async function(deployer) { // Calldatas: The function you want to call should be encoded // Description - const DESCRIPTION_HASH = ''//note bytes32 + const DESCRIPTION_HASH_STRING = ''//note bytes32 + const DESCRIPTION_HASH = web3.utils.keccak256(DESCRIPTION_HASH_STRING) const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const encodedFunctionToCall = _encodedFucntionToCall() @@ -84,17 +87,8 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - let queueProposalTxn = { - queueProposalTxnIdx: tx - } - - let data = JSON.stringify(queueProposalTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + + await txnHelper.saveTxnIndex("queueProposalTxn", tx) } await _queueProposal( diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 7dea521..158be31 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -2,6 +2,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const COUNCIL_1_PLACEHOLDER = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; @@ -40,4 +41,6 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); + await txnHelper.saveTxnIndex("setupMultisigOwner", txIndex) + } \ No newline at end of file diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index ae86cb4..7685723 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -2,6 +2,8 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/transactionSaver') +const constants = require('./helpers/constants') const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); @@ -99,4 +101,6 @@ module.exports = async function(deployer) { let txIndexCreateLock = eventsHelper.getIndexedEventArgs(resultCreateLock, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndexCreateLock, {gas: 8000000}); await multiSigWallet.executeTransaction(txIndexCreateLock, {gas: 8000000}); + await txnHelper.saveTxnIndex("createLocksForCouncilTxn", txIndexCreateLock) + } \ No newline at end of file diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index 6c50301..d3a5b01 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -2,6 +2,7 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -44,17 +45,8 @@ module.exports = async function(deployer) { const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - let stableSwapDailyLimitUpdate = { - stableSwapDailyLimitUpdateIdx: tx - } - - let data = JSON.stringify(stableSwapDailyLimitUpdate) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) + + await txnHelper.saveTxnIndex("stableSwapDailyLimitUpdate", tx) } await _updateDailySwapLimit(DAILY_LIMIT) diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index 89073be..4064ef4 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -1,7 +1,8 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); - +const txnHelper = require('./helpers/transactionSaver') +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -123,19 +124,8 @@ module.exports = async function(deployer) { let txIndexApproveFXD = await approveFXD(); let txIndexDepositUSD = await depositUSD(); let txIndexDepositFXD = await depositFXD(); - - let stableSwapTxn = { - txIndexApproveUSD: txIndexApproveUSD, - txIndexApproveFXD: txIndexApproveFXD, - txIndexDepositUSD: txIndexDepositUSD, - txIndexDepositFXD: txIndexDepositFXD, - } - - let data = JSON.stringify(stableSwapTxn) - fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ - if(err){ - console.log(err) - } - }) - + await txnHelper.saveTxnIndex("txIndexApproveUSD",txIndexApproveUSD) + await txnHelper.saveTxnIndex("txIndexApproveFXD",txIndexApproveFXD) + await txnHelper.saveTxnIndex("txIndexDepositUSD",txIndexDepositUSD) + await txnHelper.saveTxnIndex("txIndexDepositFXD",txIndexDepositFXD) } diff --git a/scripts/units/transfer-tokens.js b/scripts/units/transfer-tokens.js index bca29c1..0f31119 100644 --- a/scripts/units/transfer-tokens.js +++ b/scripts/units/transfer-tokens.js @@ -1,11 +1,12 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); - +const txnHelper = require('./helpers/transactionSaver') +const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const T_TO_TRANSFER_PLACEHOLDER = web3.utils.toWei('10000000','ether') //SET AS NEEDED -const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x4C5F0f90a2D4b518aFba11E22AC9b8F6B031d204" //SET AS NEEDED +const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x746a59A8F41DdC954542B6697954a94868126885" //SET AS NEEDED const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); @@ -29,6 +30,7 @@ const _encodeTransferFunction = (_account, t_to_stake) => { module.exports = async function(deployer) { const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + // const FATHOM_TOKEN_ADDRESS = "0x2e695811dE9E52D69574a9DF3cD53deDa9f9AbAC"; const FATHOM_TOKEN_ADDRESS = addresses.fthmToken; const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS) @@ -41,9 +43,15 @@ module.exports = async function(deployer) { 0, {gas: 8000000} ); - txIndex = eventsHelper.getIndexedEventArgs(result, constants.EMPTY_BYTES)[0]; + txIndex = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); + try{ + const executeResult = await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); + }catch(error){ + //why doesnt it catch? + } + + await txnHelper.saveTxnIndex("transferFathomTokenFromMultisig", txIndex) } await _transferFromMultiSigTreasury(TRANSFER_TO_ACCOUNT_PLACEHOLDER,T_TO_TRANSFER_PLACEHOLDER); From d180b74defce9173aa007137b4dc083b053902f2 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 13 Mar 2023 12:08:38 +0545 Subject: [PATCH 25/56] implementing stablecoin scripts - still some left --- config/external-addresses.json | 8 ++- config/newly-generated-transaction-index.json | 22 ++++++ scripts/units/create-upgrade.js | 34 +++------ scripts/units/create_pool_dex.js | 46 ++++-------- scripts/units/create_pool_dex_xdc.js | 34 +++------ .../units/create_stablecoin_open_position.js | 4 +- .../units/create_stablecoin_proxy_wallet.js | 4 +- scripts/units/create_stream.js | 71 ++++--------------- scripts/units/execute-proposals.js | 4 +- .../helpers/submitAndExecuteTransaction.js | 33 +++++++++ scripts/units/propose-proposal.js | 4 +- scripts/units/propose_stream.js | 55 ++++---------- scripts/units/queue-proposal.js | 4 +- scripts/units/setup-multisig-owners.js | 26 ++----- scripts/units/setup_council_stakes.js | 33 +++------ .../book-keeper/blacklist-bookkeeper.js | 29 ++++++++ .../book-keeper/whitelist-bookkeeper.js | 29 ++++++++ .../fathom-proxy-admin/upgrade-proxy.js | 37 ++++++++++ .../flash-mint-module/set-fee-rate.js | 28 ++++++++ .../position-manager/set-price-oracle.js | 30 ++++++++ .../position-manager/stableswap-pause.js | 25 +++++++ .../position-manager/stableswap-unpause.js | 25 +++++++ .../price-oracle/price-oracle-cage.js | 25 +++++++ .../price-oracle/price-oracle-pause.js | 25 +++++++ .../price-oracle/price-oracle-uncage.js | 25 +++++++ .../price-oracle/price-oracle-unpause.js | 25 +++++++ .../stablecoin/price-oracle/set-price.js | 29 ++++++++ .../set-stable-coin-reference-price.js | 29 ++++++++ .../proxy-actions-storage/set-proxy-action.js | 29 ++++++++ .../units/stablecoin/showstopper/cage-pool.js | 30 ++++++++ .../showstopper/cage-showstopper.js | 25 +++++++ .../stablecoin/showstopper/set-book-keeper.js | 30 ++++++++ .../showstopper/set-cage-cool-down.js | 30 ++++++++ .../showstopper/set-liquidation-engine.js | 27 +++++++ .../showstopper/set-price-oracle.js | 30 ++++++++ .../showstopper/set-system-debt-engine.js | 30 ++++++++ .../collect_stability_fee.js | 27 +++++++ .../set-system-debt-engine.js | 30 ++++++++ .../stability-fee-pause.js | 25 +++++++ .../stability-fee-unpause.js | 25 +++++++ .../stablecoin/stableswap/deposit-token.js | 1 + .../stableswap/emergency-withdraw.js | 28 ++++++++ .../units/stablecoin/stableswap/set-fee-in.js | 28 ++++++++ .../stablecoin/stableswap/set-fee-out.js | 28 ++++++++ .../stablecoin/stableswap/stableswap-pause.js | 25 +++++++ .../stableswap/stableswap-unpause.js | 25 +++++++ .../stablecoin/stableswap/withdraw-fees.js | 28 ++++++++ .../system-debt-engine-pause.js | 25 +++++++ .../system-debt-engine-unpause.js | 25 +++++++ .../withdraw-collateral-surplus.js | 41 +++++++++++ .../units/stableswap-daily-limit-update.js | 35 +++------ scripts/units/stableswap-setup.js | 10 +-- scripts/units/transfer-tokens.js | 36 ++-------- 53 files changed, 1122 insertions(+), 294 deletions(-) create mode 100644 scripts/units/helpers/submitAndExecuteTransaction.js create mode 100644 scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js create mode 100644 scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js create mode 100644 scripts/units/stablecoin/fathom-proxy-admin/upgrade-proxy.js create mode 100644 scripts/units/stablecoin/flash-mint-module/set-fee-rate.js create mode 100644 scripts/units/stablecoin/position-manager/set-price-oracle.js create mode 100644 scripts/units/stablecoin/position-manager/stableswap-pause.js create mode 100644 scripts/units/stablecoin/position-manager/stableswap-unpause.js create mode 100644 scripts/units/stablecoin/price-oracle/price-oracle-cage.js create mode 100644 scripts/units/stablecoin/price-oracle/price-oracle-pause.js create mode 100644 scripts/units/stablecoin/price-oracle/price-oracle-uncage.js create mode 100644 scripts/units/stablecoin/price-oracle/price-oracle-unpause.js create mode 100644 scripts/units/stablecoin/price-oracle/set-price.js create mode 100644 scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js create mode 100644 scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js create mode 100644 scripts/units/stablecoin/showstopper/cage-pool.js create mode 100644 scripts/units/stablecoin/showstopper/cage-showstopper.js create mode 100644 scripts/units/stablecoin/showstopper/set-book-keeper.js create mode 100644 scripts/units/stablecoin/showstopper/set-cage-cool-down.js create mode 100644 scripts/units/stablecoin/showstopper/set-liquidation-engine.js create mode 100644 scripts/units/stablecoin/showstopper/set-price-oracle.js create mode 100644 scripts/units/stablecoin/showstopper/set-system-debt-engine.js create mode 100644 scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js create mode 100644 scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js create mode 100644 scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js create mode 100644 scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js create mode 100644 scripts/units/stablecoin/stableswap/deposit-token.js create mode 100644 scripts/units/stablecoin/stableswap/emergency-withdraw.js create mode 100644 scripts/units/stablecoin/stableswap/set-fee-in.js create mode 100644 scripts/units/stablecoin/stableswap/set-fee-out.js create mode 100644 scripts/units/stablecoin/stableswap/stableswap-pause.js create mode 100644 scripts/units/stablecoin/stableswap/stableswap-unpause.js create mode 100644 scripts/units/stablecoin/stableswap/withdraw-fees.js create mode 100644 scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js create mode 100644 scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js create mode 100644 scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js diff --git a/config/external-addresses.json b/config/external-addresses.json index 7e5e1d5..118ac12 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -8,5 +8,11 @@ "PROXY_WALLET_REGISTRY_ADDRESS":"0x06063CeB65f66A678812e753785D00237F60564A", "STABLE_SWAP_ADDRESS":"0xa47232D1c5608996D4168222c45ed17E3947a50a", "USD_ADDRESS":"0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f", - "FXD_ADDRESS":"0x429758F17E06eD5cF60f4382442592021158B578" + "FXD_ADDRESS":"0x429758F17E06eD5cF60f4382442592021158B578", + "SHOW_STOPPER_ADDRESS":"", + "SYSTEM_DEBT_ENGINE_ADDRESS":"", + "STABILIITY_FEE_COLLECTOR_ADDRESS":"", + "PRICE_ORACLE_ADDRESS":"", + "BOOK_KEEPER_ADDRESS":"", + "POSITION_MANAGER_ADDRESS":"" } \ No newline at end of file diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index d923aaa..d5053f7 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -4,5 +4,27 @@ "id": 1, "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000005" } + ], + "transferFathomTokenFromMultisig": [ + { + "id": 1, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000006" + } + ], + "ApproveDexXDC": [ + { + "id": 1, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000007" + } + ], + "createPoolWithXDC": [ + { + "id": 1, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000009" + }, + { + "id": 2, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000000b" + } ] } \ No newline at end of file diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index ae1f401..9f625f9 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -1,9 +1,10 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -30,29 +31,12 @@ const _encodeUpgradeFunction = (_proxy, _impl) => { module.exports = async function(deployer) { - const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; - const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); - - const _upgrade = async( - _proxy, - _impl - ) => { - const result = await multiSigWallet.submitTransaction( - PROXY_ADMIN, - constants.EMPTY_BYTES, - _encodeUpgradeFunction( - _proxy, - _impl - ),0,{gas:8000000} - ) - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - await txnHelper.saveTxnIndex("upgradeTxn",tx) - } - - await _upgrade( - PROXY, - IMPLEMENTATION_ADDRESS + await txnHelper.submitAndExecute( + _encodeUpgradeFunction( + PROXY, + IMPLEMENTATION_ADDRESS + ), + PROXY_ADMIN, + "upgradeTxn" ) } \ No newline at end of file diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 74cda82..f3f2acd 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -1,8 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const txnHelper = require('./helpers/transactionSaver') -const eventsHelper = require("../tests/helpers/eventsHelper"); +const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -105,33 +104,21 @@ module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); //Will need to change it once it expires const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it - let resultApprove_A = await multiSigWallet.submitTransaction( - Token_A_Address, - constants.EMPTY_BYTES, + + await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), - 0, - {gas: 8000000} + Token_A_Address, + "ApproveTokenA", + 0 ) - let txIndexApprove_A = eventsHelper.getIndexedEventArgs(resultApprove_A, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexApprove_A, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexApprove_A, {gas: 8000000}); - - let resultApprove_B = await multiSigWallet.submitTransaction( - Token_B_Address, - constants.EMPTY_BYTES, + await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_B_Desired), - 0, - {gas: 8000000} + Token_B_Address, + "ApproveTokenB", + 0 ) - - let txIndexApprove_B = eventsHelper.getIndexedEventArgs(resultApprove_B, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexApprove_B, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexApprove_B, {gas: 8000000}); - - let resultAddLiquidity = await multiSigWallet.submitTransaction( - DEX_ROUTER_ADDRESS, - constants.EMPTY_BYTES, + await txnHelper.submitAndExecute( _encodeAddLiqudityFunction( Token_A_Address, Token_B_Address, @@ -142,15 +129,10 @@ module.exports = async function(deployer) { multiSigWallet.address, deadline ), - 0, - {gas: 8000000} + DEX_ROUTER_ADDRESS, + "ApproveToken", + 0 ) - - let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 8000000}); - - await txnHelper.saveTxnIndex("createPoolWithTokenPair", txIndexAddLiquidity) } diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 7dfe007..9763222 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -1,18 +1,16 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') - +const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); -const TOKEN_ADDRESS = "0x746a59A8F41DdC954542B6697954a94868126885" //FTHM address +const TOKEN_ADDRESS = "0xB01CF32918a010Dd35BDA8197F836c7D2447Ae58" //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') -const AMOUNT_ETH_MIN = web3.utils.toWei('0', 'ether') +const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS @@ -83,21 +81,14 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it - - let resultApprove = await multiSigWallet.submitTransaction( - TOKEN_ADDRESS, - constants.EMPTY_BYTES, + + await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_TOKEN_DESIRED), - 0, - {gas: 8000000} + TOKEN_ADDRESS, + "ApproveDexXDC" ) - let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexApprove, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); - let resultAddLiquidity = await multiSigWallet.submitTransaction( - DEX_ROUTER_ADDRESS, - TOKEN_ETH, + await txnHelper.submitAndExecute( _encodeAddLiqudityFunction( TOKEN_ADDRESS, AMOUNT_TOKEN_DESIRED, @@ -106,13 +97,10 @@ module.exports = async function(deployer) { multiSigWallet.address, deadline ), - 0, - {gas: 8000000} + DEX_ROUTER_ADDRESS, + "createPoolWithXDC", + TOKEN_ETH ) - let txIndexAddLiquidity = eventsHelper.getIndexedEventArgs(resultAddLiquidity, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexAddLiquidity, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexAddLiquidity, {gas: 15000000}); - await txnHelper.saveTxnIndex("createPoolWithXDC", txIndexAddLiquidity) } diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index c2431f2..3c77d27 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -1,7 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -109,5 +109,5 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(txExecute, {gas: 8000000}); await multiSigWallet.executeTransaction(txExecute, {gas: 8000000}); - await txnHelper.saveTxnIndex("openPositionTransaction", txExecute) + await txnSaver.saveTxnIndex("openPositionTransaction", txExecute) } \ No newline at end of file diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index 2e7ea0b..6a59ac2 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -1,7 +1,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -51,5 +51,5 @@ module.exports = async function(deployer) { } }) - await txnHelper.saveTxnIndex("proxyWalletTxn", txIndexBuild) + await txnSaver.saveTxnIndex("proxyWalletTxn", txIndexBuild) } \ No newline at end of file diff --git a/scripts/units/create_stream.js b/scripts/units/create_stream.js index 1ccc63b..ec37cdc 100644 --- a/scripts/units/create_stream.js +++ b/scripts/units/create_stream.js @@ -1,10 +1,8 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const txnHelper = require('./helpers/transactionSaver') +const txnHelper = require('./helpers/submitAndExecuteTransaction') -const eventsHelper = require("../tests/helpers/eventsHelper"); -const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const STREAM_REWARD_TOKEN_ADDRESS = "" @@ -45,63 +43,24 @@ const _encodeCreateStreamFunction = ( } module.exports = async function(deployer) { - const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; - const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); - let approveStreamRewardsTxnIdx; - let createStreamRewardsTxnIdx; - - const _approveStreamRewards = async( - _account, - _amount - ) => { - const result = await multiSigWallet.submitTransaction( - STREAM_REWARD_TOKEN_ADDRESS, - constants.EMPTY_BYTES, - _encodeApproveFunction( - _account, - _amount - ), - 0, - {gas: 8000000} - ) - - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - approveStreamRewardsTxnIdx = tx; - } - - const _createStreamRewards = async( - _streamId, _rewardTokenAmount - ) => { - const result = await multiSigWallet.submitTransaction( + + await txnHelper.submitAndExecute( + _encodeApproveFunction( addresses.staking, - constants.EMPTY_BYTES, - _encodeCreateStreamFunction( - _streamId, - _rewardTokenAmount - ), - 0, - {gas: 8000000} - ) - - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - createStreamRewardsTxnIdx = tx; - } - - await _approveStreamRewards( - addresses.staking, - REWARD_PROPOSAL_AMOUNT + REWARD_PROPOSAL_AMOUNT + ), + STREAM_REWARD_TOKEN_ADDRESS, + "approveStreamRewardsTxn" ) - await _createStreamRewards( - STREAM_ID, - REWARD_PROPOSAL_AMOUNT + await txnHelper.submitAndExecute( + _encodeCreateStreamFunction( + STREAM_ID, + REWARD_PROPOSAL_AMOUNT), + addresses.staking, + "createStreamRewardsTxn" ) - await txnHelper.saveTxnIndex("approveStreamRewardsTxn", approveStreamRewardsTxnIdx) - await txnHelper.saveTxnIndex("createStreamRewardsTxn", createStreamRewardsTxnIdx) + } diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index 4c06558..65fa260 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -1,7 +1,7 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -92,7 +92,7 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - await txnHelper.saveTxnIndex("executeProposalTxn",tx) + await txnSaver.saveTxnIndex("executeProposalTxn",tx) } await _executeProposal( diff --git a/scripts/units/helpers/submitAndExecuteTransaction.js b/scripts/units/helpers/submitAndExecuteTransaction.js new file mode 100644 index 0000000..d61f575 --- /dev/null +++ b/scripts/units/helpers/submitAndExecuteTransaction.js @@ -0,0 +1,33 @@ +const fs = require('fs'); +const constants = require('./constants') +const txnSaver = require('./transactionSaver') + +const eventsHelper = require("../../tests/helpers/eventsHelper"); + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const rawdata = fs.readFileSync('../../../addresses.json'); +const addresses = JSON.parse(rawdata); + +async function submitAndExecute(encodedFunction, targetAddress, TransactionName, ETH_AMOUNT=0) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; + const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); + + const _submitAndExecute = async() => { + const result = await multiSigWallet.submitTransaction( + targetAddress, + ETH_AMOUNT==0?constants.EMPTY_BYTES:ETH_AMOUNT, + encodedFunction + ,0,{gas:8000000} + ) + const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; + await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + await txnSaver.saveTxnIndex(TransactionName,tx) + } + + await _submitAndExecute() +} + +module.exports = { + submitAndExecute +} \ No newline at end of file diff --git a/scripts/units/propose-proposal.js b/scripts/units/propose-proposal.js index 6972d09..baa6a52 100644 --- a/scripts/units/propose-proposal.js +++ b/scripts/units/propose-proposal.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); @@ -87,7 +87,7 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - await txnHelper.saveTxnIndex( + await txnSaver.saveTxnIndex( "proposeProposalTxn",tx ) } diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 2530afa..8497009 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -1,5 +1,6 @@ const fs = require('fs'); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') +const txnHelper = require('./helpers/submitAndExecuteTransaction') const eventsHelper = require("../tests/helpers/eventsHelper"); const constants = require('./helpers/constants') @@ -84,45 +85,19 @@ module.exports = async function(deployer) { web3.utils.toWei("0", 'ether') ]; - const _proposeStreamFromMultiSig = async(_owner, - _rewardToken, - _maxDepositedAmount, - _minDepositedAmount, - _scheduleTimes, - _scheduleRewards, - _tau - ) => - { - const result = await multiSigWallet.submitTransaction( - addresses.staking, - constants.EMPTY_BYTES, - _encodeProposeStreamFunction( - _owner, - _rewardToken, - _maxDepositedAmount, - _minDepositedAmount, - _scheduleTimes, - _scheduleRewards, - _tau - ), - 0, - {gas: 8000000} - ) - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - await txnHelper.saveTxnIndex("proposeStreamTxn",tx) - } - - await _proposeStreamFromMultiSig( - STREAM_OWNER, - REWARD_TOKEN_ADDRESS, - MAX_DEPOSIT_AMOUNT, - MIN_DEPOSIT_AMOUNT, - scheduleTimes, - scheduleRewards, - tau + await txnHelper.submitAndExecute( + _encodeProposeStreamFunction( + STREAM_OWNER, + REWARD_TOKEN_ADDRESS, + MAX_DEPOSIT_AMOUNT, + MIN_DEPOSIT_AMOUNT, + scheduleTimes, + scheduleRewards, + tau + ), + addresses.staking, + "proposeStreamTxn" ) + } \ No newline at end of file diff --git a/scripts/units/queue-proposal.js b/scripts/units/queue-proposal.js index a4dd31e..61ae08d 100644 --- a/scripts/units/queue-proposal.js +++ b/scripts/units/queue-proposal.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const eventsHelper = require("../tests/helpers/eventsHelper"); const constants = require('./helpers/constants') @@ -88,7 +88,7 @@ module.exports = async function(deployer) { await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - await txnHelper.saveTxnIndex("queueProposalTxn", tx) + await txnSaver.saveTxnIndex("queueProposalTxn", tx) } await _queueProposal( diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 158be31..09be468 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -1,14 +1,10 @@ //NOTE: Do this at the very end as other scripts wont execute after this is done due to owners signatures requirement const fs = require('fs'); const constants = require('./helpers/constants') -const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') +const txnHelper = require('./helpers/submitAndExecuteTransaction') -const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const COUNCIL_1_PLACEHOLDER = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; const COUNCIL_2_PLACEHOLDER = "0x01d2D3da7a42F64e7Dc6Ae405F169836556adC86"; -const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); -const addresses = JSON.parse(rawdata); const _encodeAddOwnersFunction = (_accounts) => { @@ -26,21 +22,11 @@ const _encodeAddOwnersFunction = (_accounts) => { } module.exports = async function(deployer) { - const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; - const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); - - let result = await multiSigWallet.submitTransaction( - MULTISIG_WALLET_ADDRESS, - constants.EMPTY_BYTES, + + await txnHelper.submitAndExecute( _encodeAddOwnersFunction([COUNCIL_1_PLACEHOLDER, COUNCIL_2_PLACEHOLDER]), - 0, - {gas: 8000000} - ); - - txIndex = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - - await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); - await txnHelper.saveTxnIndex("setupMultisigOwner", txIndex) + MULTISIG_WALLET_ADDRESS, + "setupMultisigOwner" + ) } \ No newline at end of file diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index 7685723..9284d46 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -1,10 +1,8 @@ //NOTE: This script can be run only once for each deployment as making COUNCIL_STAKES is only possible once const fs = require('fs'); -const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') const constants = require('./helpers/constants') - +const txnHelper = require('./helpers/submitAndExecuteTransaction') const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -70,37 +68,22 @@ const _encodeCreateLocksForCouncils = (_createLockParam) => { module.exports = async function(deployer) { const stakingService = await IStaking.at(addresses.staking); - const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - let resultApprove = await multiSigWallet.submitTransaction( - addresses.fthmToken, - constants.EMPTY_BYTES, + + await txnHelper.submitAndExecute( _encodeApproveFunction(stakingService.address,T_TOTAL_TO_APPROVE), - 0, - {gas: 8000000} + addresses.fthmToken, + "ApproveFathomTxn" ) - - let txIndexApprove = eventsHelper.getIndexedEventArgs(resultApprove, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexApprove, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexApprove, {gas: 8000000}); const LockParamObjectForAllCouncils = [ _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_1), _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_2), _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_3) ] - let resultCreateLock = await multiSigWallet.submitTransaction( - stakingService.address, - constants.EMPTY_BYTES, + await txnHelper.submitAndExecute( _encodeCreateLocksForCouncils(LockParamObjectForAllCouncils), - 0, - {gas: 8000000} + stakingService.address, + "createLocksForCouncilTxn" ) - - - let txIndexCreateLock = eventsHelper.getIndexedEventArgs(resultCreateLock, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndexCreateLock, {gas: 8000000}); - await multiSigWallet.executeTransaction(txIndexCreateLock, {gas: 8000000}); - await txnHelper.saveTxnIndex("createLocksForCouncilTxn", txIndexCreateLock) - } \ No newline at end of file diff --git a/scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js new file mode 100644 index 0000000..a5720f1 --- /dev/null +++ b/scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS +const TO_BE_BLACKLISTED = "0x" +const _encodeBlacklist = (toBeBlacklistedAddress) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'blacklist', + type: 'function', + inputs: [{ + type: 'address', + name: 'toBeBlacklistedAddress' + }] + }, [toBeBlacklistedAddress]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeBlacklist(TO_BE_BLACKLISTED), + BOOK_KEEPER_ADDRESS, + "setBlacklistedAddressBookkeeper" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js new file mode 100644 index 0000000..e0c1748 --- /dev/null +++ b/scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS +const TO_BE_WHITELISTED = "0x" +const _encodeWhitelist = (toBeWhitelistedAddress) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'whitelist', + type: 'function', + inputs: [{ + type: 'address', + name: 'toBeWhitelistedAddress' + }] + }, [toBeWhitelistedAddress]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeWhitelist(TO_BE_WHITELISTED), + BOOK_KEEPER_ADDRESS, + "setWhitelistedAddressBookkeeper" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/fathom-proxy-admin/upgrade-proxy.js b/scripts/units/stablecoin/fathom-proxy-admin/upgrade-proxy.js new file mode 100644 index 0000000..49defb6 --- /dev/null +++ b/scripts/units/stablecoin/fathom-proxy-admin/upgrade-proxy.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') + + +const txnHelper = require('./helpers/submitAndExecuteTransaction') + +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const FATHOM_PROXY_ADMIN = "0xB7a8f3A8178B21499b56d9d054119821953d2C3f" +const PROXY = "0xFD21E72b63568942E541284D275ce1057e7F1257" +const IMPLEMENTATION_ADDRESS = "0xa5B675dd61c00C41F3FA5b919b7E917A61dbE7f7" +const _encodeUpgradeFunction = (_proxy, _impl) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'upgrade', + type: 'function', + inputs: [{ + type: 'address', + name: 'proxy' + },{ + type: 'address', + name: 'implementation' + }] + }, [_proxy, _impl]); + + return toRet; +} + + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeUpgradeFunction( + PROXY, + IMPLEMENTATION_ADDRESS + ), + FATHOM_PROXY_ADMIN, + "upgradeStablecoinProxy" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js new file mode 100644 index 0000000..5e76c5c --- /dev/null +++ b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const FEE_RATE =1 +const FLASH_MINT_MODULE_ADDRESS = addressesExternal.FLASH_MINT_MODULE_ADDRESS + +const _encodeSetFeeRate = (_data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setFeeRate', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_data' + }] + }, [_data]); + + return toRet; +} + + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetFeeRate(_data), + FLASH_MINT_MODULE_ADDRESS, + "setFeeRate" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/position-manager/set-price-oracle.js b/scripts/units/stablecoin/position-manager/set-price-oracle.js new file mode 100644 index 0000000..b93303c --- /dev/null +++ b/scripts/units/stablecoin/position-manager/set-price-oracle.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE = "" +const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS + +const _encodeSetPriceOracle = (_priceOracle) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPriceOracle', + type: 'function', + inputs: [{ + type: 'address', + name: '_priceOracle' + }] + }, [_priceOracle]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetPriceOracle(PRICE_ORACLE), + POSITION_MANAGER_ADDRESS, + "setPriceOracle-Position-manager" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/position-manager/stableswap-pause.js b/scripts/units/stablecoin/position-manager/stableswap-pause.js new file mode 100644 index 0000000..c1da953 --- /dev/null +++ b/scripts/units/stablecoin/position-manager/stableswap-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + POSITION_MANAGER_ADDRESS, + "pausePositionManager" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/position-manager/stableswap-unpause.js b/scripts/units/stablecoin/position-manager/stableswap-unpause.js new file mode 100644 index 0000000..ba7249e --- /dev/null +++ b/scripts/units/stablecoin/position-manager/stableswap-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS + +const _encodeUnpause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnpause(), + POSITION_MANAGER_ADDRESS, + "unpausePositionManager" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-cage.js b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js new file mode 100644 index 0000000..81a0714 --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS + +const _encodeCage = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'cage', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeCage(), + PRICE_ORACLE_ADDRESS, + "cagePriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-pause.js b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js new file mode 100644 index 0000000..6da7971 --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + PRICE_ORACLE_ADDRESS, + "pausePriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js new file mode 100644 index 0000000..b7cb546 --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS + +const _encodeUncage = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'cage', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUncage(), + PRICE_ORACLE_ADDRESS, + "uncagePriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js new file mode 100644 index 0000000..d13e1f4 --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS + +const _encodeUnpause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnpause(), + PRICE_ORACLE_ADDRESS, + "unpausePriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/set-price.js b/scripts/units/stablecoin/price-oracle/set-price.js new file mode 100644 index 0000000..707ebd2 --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/set-price.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const COLLATERAL_POOL_ID = 123 +const _encodeSetPrice = (_collateralPoolId) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPrice', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + }] + }, [_collateralPoolId]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetPrice(COLLATERAL_POOL_ID), + PRICE_ORACLE_ADDRESS, + "setPrice-PriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js new file mode 100644 index 0000000..59e326a --- /dev/null +++ b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const DATA = 123 +const _encodeSetStableCoinReferencePrice = (_data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setStableCoinReferencePrice', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_data' + }] + }, [_data]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetStableCoinReferencePrice(DATA), + PRICE_ORACLE_ADDRESS, + "setStablecoinReferencePrice" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js new file mode 100644 index 0000000..47bf803 --- /dev/null +++ b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PROXY_ACTION_STORAGE_ADDRESS =addressesExternal.PROXY_ACTION_STORAGE_ADDRESS +const PROXY_ACTION_ADDRESS = "0x" +const _encodeSetProxyAction = (_proxyAction) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setProxyAction', + type: 'function', + inputs: [{ + type: 'address', + name: '_proxyAction' + }] + }, [_proxyAction]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetProxyAction(PROXY_ACTION_STORAGE_ADDRESS), + PROXY_ACTION_ADDRESS, + "setProxyActionAddress" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/cage-pool.js b/scripts/units/stablecoin/showstopper/cage-pool.js new file mode 100644 index 0000000..6696840 --- /dev/null +++ b/scripts/units/stablecoin/showstopper/cage-pool.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const COLLATERAL_POOL_ID = '' +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeCagePool = (_collateralPoolId) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'cagePool', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + }] + }, [_collateralPoolId]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeCagePool(COLLATERAL_POOL_ID), + SHOW_STOPPER_ADDRESS, + "cageCollateralPool" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/cage-showstopper.js b/scripts/units/stablecoin/showstopper/cage-showstopper.js new file mode 100644 index 0000000..7f1e6ea --- /dev/null +++ b/scripts/units/stablecoin/showstopper/cage-showstopper.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeCage = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'cage', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeCage(), + SHOW_STOPPER_ADDRESS, + "cageShowStopper" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/set-book-keeper.js b/scripts/units/stablecoin/showstopper/set-book-keeper.js new file mode 100644 index 0000000..97bde13 --- /dev/null +++ b/scripts/units/stablecoin/showstopper/set-book-keeper.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const BOOK_KEEPER_ADDRESS = "" +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeSetBookKeeper = (_bookkeeper) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setBookKeeper', + type: 'function', + inputs: [{ + type: 'address', + name: '_bookkeeper' + }] + }, [_bookkeeper]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetBookKeeper(BOOK_KEEPER_ADDRESS), + SHOW_STOPPER_ADDRESS, + "setBookKeeper" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/set-cage-cool-down.js b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js new file mode 100644 index 0000000..da3efaa --- /dev/null +++ b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const CAGE_COOL_DOWN =1 +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeSetCageCooldown = (_cageCoolDown) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setCageCoolDown', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_cageCoolDown' + }] + }, [_cageCoolDown]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetCageCooldown(CAGE_COOL_DOWN), + SHOW_STOPPER_ADDRESS, + "setCageCooldown" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/set-liquidation-engine.js b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js new file mode 100644 index 0000000..9df3795 --- /dev/null +++ b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const LIQUIDATION_ENGINE_ADDRESS = "" +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const _encodeLiquidationEngine = (_liquidationEngine) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setLiquidationEngine', + type: 'function', + inputs: [{ + type: 'address', + name: '_liquidationEngine' + }] + }, [_liquidationEngine]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeLiquidationEngine(LIQUIDATION_ENGINE_ADDRESS), + SHOW_STOPPER_ADDRESS, + "setLiquidationEngine" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/set-price-oracle.js b/scripts/units/stablecoin/showstopper/set-price-oracle.js new file mode 100644 index 0000000..8ca9061 --- /dev/null +++ b/scripts/units/stablecoin/showstopper/set-price-oracle.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const PRICE_ORACLE = "" +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeSetPriceOracle = (_priceOracle) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPriceOracle', + type: 'function', + inputs: [{ + type: 'address', + name: '_priceOracle' + }] + }, [_priceOracle]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetPriceOracle(PRICE_ORACLE), + SHOW_STOPPER_ADDRESS, + "setPriceOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/showstopper/set-system-debt-engine.js b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js new file mode 100644 index 0000000..6a1cc54 --- /dev/null +++ b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SYSTEM_DEBT_ENGINE = "" +const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS + +const _encodeSetSystemDebtEngine = (_systemDebtEngine) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setSystemDebtEngine', + type: 'function', + inputs: [{ + type: 'address', + name: '_systemDebtEngine' + }] + }, [_systemDebtEngine]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetSystemDebtEngine(SYSTEM_DEBT_ENGINE), + SHOW_STOPPER_ADDRESS, + "setSystemDebtEngine" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js new file mode 100644 index 0000000..5e666bd --- /dev/null +++ b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS + +const _encodeCollect = (_collateralPool) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'collect', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPool' + }] + }, [_collateralPool]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeCollect(_collateralPool), + STABILITY_FEE_COLLECTOR_ADDRESS, + "CollectStabilityFee" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js new file mode 100644 index 0000000..d1bd97b --- /dev/null +++ b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SYSTEM_DEBT_ENGINE_ADDRESS = "" +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS + +const _encodeSetBookKeeper = (_systemDebtEngine) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setSystemDebtEngine', + type: 'function', + inputs: [{ + type: 'address', + name: '_systemDebtEngine' + }] + }, [_systemDebtEngine]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetBookKeeper(SYSTEM_DEBT_ENGINE_ADDRESS), + STABILITY_FEE_COLLECTOR_ADDRESS, + "setStabilityFeeCollector" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js new file mode 100644 index 0000000..6ea7f9e --- /dev/null +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + STABILITY_FEE_COLLECTOR_ADDRESS, + "pauseStabilityFeeCollector" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js new file mode 100644 index 0000000..fc37f21 --- /dev/null +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS + +const _encodeUnpause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnpause(), + STABILITY_FEE_COLLECTOR_ADDRESS, + "unpauseStabilityFeeCollector" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/deposit-token.js b/scripts/units/stablecoin/stableswap/deposit-token.js new file mode 100644 index 0000000..ea64188 --- /dev/null +++ b/scripts/units/stablecoin/stableswap/deposit-token.js @@ -0,0 +1 @@ +//TODO \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/emergency-withdraw.js b/scripts/units/stablecoin/stableswap/emergency-withdraw.js new file mode 100644 index 0000000..9dbf48a --- /dev/null +++ b/scripts/units/stablecoin/stableswap/emergency-withdraw.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const ACCOUNT_DESTINATION = "" +const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS + +const _encodeEmergencyWithdraw = (_destination) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'emergencyWithdraw', + type: 'function', + inputs: [{ + type: 'address', + name: '_account' + }] + }, [_destination]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeEmergencyWithdraw(ACCOUNT_DESTINATION), + STABLE_SWAP_ADDRESS, + "StableswapEmergencyWithdraw" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/set-fee-in.js b/scripts/units/stablecoin/stableswap/set-fee-in.js new file mode 100644 index 0000000..3f6ddfd --- /dev/null +++ b/scripts/units/stablecoin/stableswap/set-fee-in.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const FEE_IN = 1 +const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS + +const _encodeSetFeeIn = (_feeIn) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setFeeIn', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_feeIn' + }] + }, [_feeIn]); + + return toRet; +} + + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetFeeIn(FEE_IN), + STABLE_SWAP_ADDRESS, + "setStableSwapFeeIn" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/set-fee-out.js b/scripts/units/stablecoin/stableswap/set-fee-out.js new file mode 100644 index 0000000..fc80419 --- /dev/null +++ b/scripts/units/stablecoin/stableswap/set-fee-out.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const FEE_OUT = 1 +const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS + +const _encodeSetFeeIn = (_feeOut) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setFeeOut', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_feeOut' + }] + }, [_feeOut]); + + return toRet; +} + + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetFeeIn(FEE_OUT), + STABLE_SWAP_ADDRESS, + "setStableSwapFeeOut" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/stableswap-pause.js b/scripts/units/stablecoin/stableswap/stableswap-pause.js new file mode 100644 index 0000000..262684b --- /dev/null +++ b/scripts/units/stablecoin/stableswap/stableswap-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + STABLE_SWAP_ADDRESS, + "pauseStableSwap" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/stableswap-unpause.js b/scripts/units/stablecoin/stableswap/stableswap-unpause.js new file mode 100644 index 0000000..9b93421 --- /dev/null +++ b/scripts/units/stablecoin/stableswap/stableswap-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS + +const _encodeUnpause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnpause(), + STABLE_SWAP_ADDRESS, + "unpauseStableSwap" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/stableswap/withdraw-fees.js b/scripts/units/stablecoin/stableswap/withdraw-fees.js new file mode 100644 index 0000000..a76d92f --- /dev/null +++ b/scripts/units/stablecoin/stableswap/withdraw-fees.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const _destination = "" +const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS + +const _encodeWithdrawFees = (_destination) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'withdrawFees', + type: 'function', + inputs: [{ + type: 'address', + name: '_destination' + }] + }, [_destination]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeWithdrawFees(_destination), + STABLE_SWAP_ADDRESS, + "StableswapWithdrawFees" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js b/scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js new file mode 100644 index 0000000..0be08cc --- /dev/null +++ b/scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + SYSTEM_DEBT_ENGINE_ADDRESS, + "pauseSystemDebtEngine" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js b/scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js new file mode 100644 index 0000000..ca7decf --- /dev/null +++ b/scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS + +const _encodeUnause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnause(), + SYSTEM_DEBT_ENGINE_ADDRESS, + "unpauseSystemDebtEngine" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js b/scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js new file mode 100644 index 0000000..62f4e74 --- /dev/null +++ b/scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js @@ -0,0 +1,41 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRSESS +const COLLATERAL_POOL_ID = '' +const ADAPTER = '' +const TO = '' +const AMOUNT = web3.utils.toWei('1','ether'); +const _encodeWithdrawCollateralSurplus = (_collateralPoolId,_adapter,_to,_amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setBookKeeper', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'address', + name: '_adapter' + },{ + type: 'address', + name: '_to' + },{ + type: 'uint256', + name: '_amount' + }] + }, [_collateralPoolId,_adapter,_to,_amount]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeWithdrawCollateralSurplus(COLLATERAL_POOL_ID,ADAPTER,TO,AMOUNT), + SYSTEM_DEBT_ENGINE_ADDRESS, + "withdrawCollateralPlus" + ) +} \ No newline at end of file diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index d3a5b01..e50d2b7 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -1,12 +1,8 @@ const fs = require('fs'); -const eventsHelper = require("../tests/helpers/eventsHelper"); - +const txnHelper = require('./helpers/submitAndExecuteTransaction') const constants = require('./helpers/constants') -const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); -const addresses = JSON.parse(rawdata); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS @@ -28,26 +24,11 @@ const _encodeUpdateDailySwapLimit = (newdailySwapLimit) =>{ } module.exports = async function(deployer) { - const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; - const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); - - const _updateDailySwapLimit = async( - newdailySwapLimit - ) => { - const result = await multiSigWallet.submitTransaction( - STABLE_SWAP_ADDRESS, - constants.EMPTY_BYTES, - _encodeUpdateDailySwapLimit( - newdailySwapLimit - ),0,{gas:8000000} - ) - - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); - - await txnHelper.saveTxnIndex("stableSwapDailyLimitUpdate", tx) - } - - await _updateDailySwapLimit(DAILY_LIMIT) + await txnHelper.submitAndExecute( + _encodeUpdateDailySwapLimit( + DAILY_LIMIT + ), + STABLE_SWAP_ADDRESS, + "stableSwapDailyLimitUpdate" + ) } \ No newline at end of file diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index 4064ef4..aed2a6a 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -1,7 +1,7 @@ const fs = require('fs'); const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') +const txnSaver = require('./helpers/transactionSaver') const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); @@ -124,8 +124,8 @@ module.exports = async function(deployer) { let txIndexApproveFXD = await approveFXD(); let txIndexDepositUSD = await depositUSD(); let txIndexDepositFXD = await depositFXD(); - await txnHelper.saveTxnIndex("txIndexApproveUSD",txIndexApproveUSD) - await txnHelper.saveTxnIndex("txIndexApproveFXD",txIndexApproveFXD) - await txnHelper.saveTxnIndex("txIndexDepositUSD",txIndexDepositUSD) - await txnHelper.saveTxnIndex("txIndexDepositFXD",txIndexDepositFXD) + await txnSaver.saveTxnIndex("txIndexApproveUSD",txIndexApproveUSD) + await txnSaver.saveTxnIndex("txIndexApproveFXD",txIndexApproveFXD) + await txnSaver.saveTxnIndex("txIndexDepositUSD",txIndexDepositUSD) + await txnSaver.saveTxnIndex("txIndexDepositFXD",txIndexDepositFXD) } diff --git a/scripts/units/transfer-tokens.js b/scripts/units/transfer-tokens.js index 0f31119..93ab7b6 100644 --- a/scripts/units/transfer-tokens.js +++ b/scripts/units/transfer-tokens.js @@ -1,13 +1,9 @@ const fs = require('fs'); -const eventsHelper = require("../tests/helpers/eventsHelper"); -const txnHelper = require('./helpers/transactionSaver') const constants = require('./helpers/constants') -const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); - const T_TO_TRANSFER_PLACEHOLDER = web3.utils.toWei('10000000','ether') //SET AS NEEDED const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x746a59A8F41DdC954542B6697954a94868126885" //SET AS NEEDED - +const txnHelper = require('./helpers/submitAndExecuteTransaction') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const _encodeTransferFunction = (_account, t_to_stake) => { @@ -28,31 +24,11 @@ const _encodeTransferFunction = (_account, t_to_stake) => { } module.exports = async function(deployer) { - - const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; - // const FATHOM_TOKEN_ADDRESS = "0x2e695811dE9E52D69574a9DF3cD53deDa9f9AbAC"; const FATHOM_TOKEN_ADDRESS = addresses.fthmToken; - const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS) - - const _transferFromMultiSigTreasury = async (_account, _value) => { - const result = await multiSigWallet.submitTransaction( - FATHOM_TOKEN_ADDRESS, - constants.EMPTY_BYTES, - _encodeTransferFunction(_account, _value), - 0, - {gas: 8000000} - ); - txIndex = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(txIndex, {gas: 8000000}); - try{ - const executeResult = await multiSigWallet.executeTransaction(txIndex, {gas: 8000000}); - }catch(error){ - //why doesnt it catch? - } - - await txnHelper.saveTxnIndex("transferFathomTokenFromMultisig", txIndex) - } - - await _transferFromMultiSigTreasury(TRANSFER_TO_ACCOUNT_PLACEHOLDER,T_TO_TRANSFER_PLACEHOLDER); + await txnHelper.submitAndExecute( + _encodeTransferFunction(TRANSFER_TO_ACCOUNT_PLACEHOLDER, T_TO_TRANSFER_PLACEHOLDER), + FATHOM_TOKEN_ADDRESS, + "transferFathomTokenFromMultisig" + ) } \ No newline at end of file From 1ae0c3266b1b7dcc246eedbee5da72ce0748cbee Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 13 Mar 2023 14:37:06 +0545 Subject: [PATCH 26/56] stablecoin scripts almost done-few left --- config/external-addresses.json | 3 +- scripts/units/helpers/unit.js | 26 ++++++ .../init-collateral-pool.js | 81 +++++++++++++++++++ .../collateral-pool-config/set-adapter.js | 30 +++++++ .../set-close-factor-bps.js | 30 +++++++ .../set-debt-accumulated-rate.js | 30 +++++++ .../set-debt-ceiling.js | 30 +++++++ .../collateral-pool-config/set-debt-floor.js | 30 +++++++ .../set-liquidation-ratio.js | 30 +++++++ .../set-liquidator-incentive-bps | 30 +++++++ .../collateral-pool-config/set-price-feed.js | 30 +++++++ .../set-price-with-safety-margin.js | 30 +++++++ .../set-stability-fee-rate.js | 30 +++++++ .../collateral-pool-config/set-strategy.js | 29 +++++++ .../set-total-debt-share.js | 30 +++++++ .../set-treausry-fees-bps.js | 30 +++++++ .../system-debt-engine/set-surplus-buffer.js | 28 +++++++ .../system-debt-engine-pause.js | 0 .../system-debt-engine-unpause.js | 0 .../withdraw-collateral-surplus.js | 0 .../withdraw-stablecoin-surplus.js | 32 ++++++++ 21 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 scripts/units/helpers/unit.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-adapter.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-price-feed.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-strategy.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js create mode 100644 scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js create mode 100644 scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js rename scripts/units/stablecoin/{systemdebtengine => system-debt-engine}/system-debt-engine-pause.js (100%) rename scripts/units/stablecoin/{systemdebtengine => system-debt-engine}/system-debt-engine-unpause.js (100%) rename scripts/units/stablecoin/{systemdebtengine => system-debt-engine}/withdraw-collateral-surplus.js (100%) create mode 100644 scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js diff --git a/config/external-addresses.json b/config/external-addresses.json index 118ac12..c3e2975 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -14,5 +14,6 @@ "STABILIITY_FEE_COLLECTOR_ADDRESS":"", "PRICE_ORACLE_ADDRESS":"", "BOOK_KEEPER_ADDRESS":"", - "POSITION_MANAGER_ADDRESS":"" + "POSITION_MANAGER_ADDRESS":"", + "COLLATERAL_POOL_CONFIG_ADDRESS":"" } \ No newline at end of file diff --git a/scripts/units/helpers/unit.js b/scripts/units/helpers/unit.js new file mode 100644 index 0000000..53d68f6 --- /dev/null +++ b/scripts/units/helpers/unit.js @@ -0,0 +1,26 @@ +const { BigNumber } = require("ethers"); + +/** + * wad: some quantity of tokens, usually as a fixed point integer with 18 decimal places. + * ray: a fixed point integer, with 27 decimal places. + * rad: a fixed point integer, with 45 decimal places. + */ + +const WeiPerBln = BigNumber.from(`1${"0".repeat(9)}`) +const WeiPerWad = BigNumber.from(`1${"0".repeat(18)}`) +const WeiPerRay = BigNumber.from(`1${"0".repeat(27)}`) +const WeiPerRad = BigNumber.from(`1${"0".repeat(45)}`) + +function weiToRay(input) { + return BigNumber.from(input.mul(WeiPerRay).div(WeiPerWad)) +} + +function weiToDecimal(input) { + return Number((input/Math.pow(10, 18)).toFixed(2)); +} + +function rayToDecimal(input) { + return Number((input/Math.pow(10, 27)).toFixed(2)); +} + +module.exports = {WeiPerWad, WeiPerBln, WeiPerRay, WeiPerRad, weiToRay, weiToDecimal, rayToDecimal} diff --git a/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js new file mode 100644 index 0000000..c12ddb0 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_ID = '' +const DEBT_CEILING = '' +const DEBT_FLOOR = '' +const PRICE_FEED = '' +const LIQUIDATION_RATIO='' +const STABILITY_FEE_RATE='' +const ADAPTER = '' +const CLOSE_FACTOR_BPS = '' +const LIQUIDATOR_INCENTIVE_BPS = '' +const TREASURY_FEE_BPS = '' +const STRATEGY = '' +const _encodeInitCollateralPool = (_collateralPoolId,_debtCeiling,_debtFloor,_priceFeed,_liquidationRatio,_stabilityFeeRate,_adapter,_closeFactorBps,_liquidatorIncentiveBps,_treasuryFeesBps,_strategy) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'initCollateralPool', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_debtCeiling' + },{ + type: 'uint256', + name: '_debtFloor' + },{ + type: 'address', + name: '_priceFeed' + },{ + type: 'uint256', + name: '_liquidationRatio' + },{ + type: 'uint256', + name: '_stabilityFeeRate' + },{ + type: 'address', + name: '_adapter' + },{ + type: 'uint256', + name: '_closeFactorBps' + },{ + type: 'uint256', + name: '_liquidatorIncentiveBps' + },{ + type: 'uint256', + name: '_treasuryFeesBps' + },{ + type: 'address', + name: '_strategy' + }] + }, [_collateralPoolId,_debtCeiling,_debtFloor,_priceFeed,_liquidationRatio,_stabilityFeeRate,_adapter,_closeFactorBps,_liquidatorIncentiveBps,_treasuryFeesBps,_strategy]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeInitCollateralPool( + COLLATERAL_POOL_ID, + DEBT_CEILING, + DEBT_FLOOR, + PRICE_FEED, + LIQUIDATION_RATIO, + STABILITY_FEE_RATE, + ADAPTER, + CLOSE_FACTOR_BPS, + LIQUIDATOR_INCENTIVE_BPS, + TREASURY_FEE_BPS, + STRATEGY), + + COLLATERAL_POOL_CONFIG_ADDRESS, + "collateralInitPool" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-adapter.js b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js new file mode 100644 index 0000000..4c5563e --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const ADAPTER = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetAdapter = (_collateralPoolId, _adapter) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setAdapter', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'address', + name: '_adapter' + }] + }, [_collateralPoolId,_adapter]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetAdapter(COLLATERAL_POOL_ID,ADAPTER), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setAdapter" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js new file mode 100644 index 0000000..8b225cc --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const CLOSE_FACTOR_BPS = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeCloseFactorBPS = (_collateralPoolId, _closeFactorBps) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setCloseFactorBps', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPool' + },{ + type: 'uint256', + name: '_closeFactorBps' + }] + }, [_collateralPoolId,_closeFactorBps]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeCloseFactorBPS(COLLATERAL_POOL_ID,CLOSE_FACTOR_BPS), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setCloseFactorBps" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js new file mode 100644 index 0000000..9d59e1a --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const DEBT_ACCUMULATED_RATE = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _debtAccumulatedRate) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setDebtAccumulatedRate', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_debtAccumulatedRate' + }] + }, [_collateralPoolId,_debtAccumulatedRate]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeLiquidatorIncentiveBPS(COLLATERAL_POOL_ID,DEBT_ACCUMULATED_RATE), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setDebtAccumulatedRate" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js new file mode 100644 index 0000000..09de663 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const DEBT_CEILING = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetDebtCeiling = (_collateralPoolId, _debtCeiling) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setDebtCeiling', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_debtCeiling' + }] + }, [_collateralPoolId,_debtCeiling]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetDebtCeiling(COLLATERAL_POOL_ID,DEBT_CEILING), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setDebtCeiling" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js new file mode 100644 index 0000000..48a3d64 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const DEBT_FLOOR = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetDebtFloor = (_collateralPoolId, _debtFloor) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setDebtFloor', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_debtFloor' + }] + }, [_collateralPoolId,_debtFloor]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetDebtFloor(COLLATERAL_POOL_ID,DEBT_FLOOR), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setDebtFloor" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js new file mode 100644 index 0000000..ac2f242 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const DATA = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetLiquidationRatio = (_collateralPoolId, _data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setLiquidationRatio', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_poolId' + },{ + type: 'uint256', + name: '_data' + }] + }, [_collateralPoolId,_data]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetDebtFloor(COLLATERAL_POOL_ID,DATA), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setLiquidationRatio" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps new file mode 100644 index 0000000..8fab4d6 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const LIQUIDATOR_INCENTIVE_BPS = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _liquidatorIncentiveBps) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setLiquidatorIncentiveBps', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_liquidatorIncentiveBps' + }] + }, [_collateralPoolId,_liquidatorIncentiveBps]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeLiquidatorIncentiveBPS(COLLATERAL_POOL_ID,LIQUIDATOR_INCENTIVE_BPS), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setLiquidatorIncentiveBps" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js new file mode 100644 index 0000000..664c4d8 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const PRICE_FEED = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetPriceFeed = (_collateralPoolId, _priceFeed) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPriceFeed', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'address', + name: '_priceFeed' + }] + }, [_collateralPoolId,_priceFeed]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetPriceFeed(COLLATERAL_POOL_ID,PRICE_FEED), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setPriceFeed" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js new file mode 100644 index 0000000..63abbd8 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const PRICE_WITH_SAFETY_MARGIN = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetPriceWithSafetyMargin = (_collateralPoolId, _priceWithSafetyMargin) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPriceWithSafetyMargin', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_priceWithSafetyMargin' + }] + }, [_collateralPoolId,_priceWithSafetyMargin]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetPriceWithSafetyMargin(COLLATERAL_POOL_ID,PRICE_WITH_SAFETY_MARGIN), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setPriceWithSafetyMargin" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js new file mode 100644 index 0000000..6fb58ea --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const STABILITY_FEE = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeStabilityFeeRate = (_collateralPoolId, _stabilityFeeRate) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setStabilityFeeRate', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPool' + },{ + type: 'uint256', + name: '_stabilityFeeRate' + }] + }, [_collateralPoolId,_stabilityFeeRate]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeStabilityFeeRate(COLLATERAL_POOL_ID,STABILITY_FEE), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setStabilityFeeRate" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-strategy.js b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js new file mode 100644 index 0000000..988fa9a --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const STRATEGY = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeSetStrategy = (_collateralPoolId, _strategy) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setStrategy', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'address', + name: '_strategy' + }] + }, [_collateralPoolId,_strategy]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetStrategy(COLLATERAL_POOL_ID,STRATEGY), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setStrategy" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js new file mode 100644 index 0000000..e800178 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const TOTAL_DEBT_SHARE = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeTotalDebtShare = (_collateralPoolId, _totalDebtShare) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setTotalDebtShare', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_totalDebtShare' + }] + }, [_collateralPoolId,_totalDebtShare]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeTotalDebtShare(COLLATERAL_POOL_ID,TOTAL_DEBT_SHARE), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setTotalDebtShare" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js new file mode 100644 index 0000000..eadf213 --- /dev/null +++ b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const COLLATERAL_POOL_ID = '' +const TREASURY_FEES_BPS = '' +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _treasuryFeesBps) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setTreasuryFeesBps', + type: 'function', + inputs: [{ + type: 'bytes32', + name: '_collateralPoolId' + },{ + type: 'uint256', + name: '_treasuryFeesBps' + }] + }, [_collateralPoolId,_treasuryFeesBps]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeLiquidatorIncentiveBPS(COLLATERAL_POOL_ID,TREASURY_FEES_BPS), + COLLATERAL_POOL_CONFIG_ADDRESS, + "setTreasuryFeesBps" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js new file mode 100644 index 0000000..88a0aa2 --- /dev/null +++ b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const DATA = 1 +const SYSTEM_DEBT_ENGINE_ADDRESS = addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS + +const _encodeSetSurplusBuffer = (_data) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setSurplusBuffer', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_data' + }] + }, [_data]); + + return toRet; +} + + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeSetSurplusBuffer(DATA), + SYSTEM_DEBT_ENGINE_ADDRESS, + "setSurplusBuffer" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js similarity index 100% rename from scripts/units/stablecoin/systemdebtengine/system-debt-engine-pause.js rename to scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js diff --git a/scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js similarity index 100% rename from scripts/units/stablecoin/systemdebtengine/system-debt-engine-unpause.js rename to scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js diff --git a/scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js similarity index 100% rename from scripts/units/stablecoin/systemdebtengine/withdraw-collateral-surplus.js rename to scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js diff --git a/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js new file mode 100644 index 0000000..12cd899 --- /dev/null +++ b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js @@ -0,0 +1,32 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); +const TO = "0x" +const VALUE = 123 +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS + +const _encodeWithdrawStablecoinSurplus = (_to,_value) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'withdrawStablecoinSurplus', + type: 'function', + inputs: [{ + type: 'address', + name: '_to' + }, + { + type: 'uint256', + name: '_value' + }] + }, [_to,_value]); + + return toRet; +} + +module.exports = async function(deployer) { + await txnHelper.submitAndExecute( + _encodeWithdrawStablecoinSurplus(TO,VALUE), + SYSTEM_DEBT_ENGINE_ADDRESS, + "WithdrawStablecoinSurplus" + ) +} \ No newline at end of file From 0cbbdc87dffb8d58234a7d193b8a305b895182c1 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 13 Mar 2023 17:53:10 +0545 Subject: [PATCH 27/56] making contracts ready for review with few final modifies --- contracts/dao/governance/Governor.sol | 1 - contracts/dao/governance/MainTokenGovernor.sol | 10 +++++----- .../governance/extensions/GovernorTimelockControl.sol | 6 +++--- .../extensions/GovernorVotesQuorumFraction.sol | 2 +- contracts/dao/staking/packages/StakingInternals.sol | 4 +--- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index d0eba9b..7dc231d 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -442,7 +442,6 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return 0; } - function hashProposal( address[] memory targets, diff --git a/contracts/dao/governance/MainTokenGovernor.sol b/contracts/dao/governance/MainTokenGovernor.sol index f414653..a79edcf 100644 --- a/contracts/dao/governance/MainTokenGovernor.sol +++ b/contracts/dao/governance/MainTokenGovernor.sol @@ -150,9 +150,9 @@ contract MainTokenGovernor is address target, bytes calldata data ) external virtual onlyGovernance { - require(isSupportedToken[target], "relayERC20: token not supported"); + require(isSupportedToken[target], "token not supported"); (bool success, bytes memory returndata) = target.call(data); - Address.verifyCallResult(success, returndata, "Governor: relayERC20 reverted without message"); + Address.verifyCallResult(success, returndata, "reverted without message"); } /** @@ -166,9 +166,9 @@ contract MainTokenGovernor is uint256 value, bytes calldata data ) external payable virtual onlyGovernance { - require(!isSupportedToken[target],"relayNativeToken: cant relay native token to supported token"); + require(!isSupportedToken[target],"cant relay native token to supported token"); (bool success, bytes memory returndata) = target.call{ value: value }(data); - Address.verifyCallResult(success, returndata, "Governor: relayNativeToken reverted without message"); + Address.verifyCallResult(success, returndata, "reverted without message"); } function _execute( @@ -178,7 +178,7 @@ contract MainTokenGovernor is bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) { - require(isConfirmed[proposalId], "MainTokenGovernor: Proposal not confirmed by council"); + require(isConfirmed[proposalId], "Proposal not confirmed"); super._execute(proposalId, targets, values, calldatas, descriptionHash); } diff --git a/contracts/dao/governance/extensions/GovernorTimelockControl.sol b/contracts/dao/governance/extensions/GovernorTimelockControl.sol index c9319e2..1f4053e 100644 --- a/contracts/dao/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/dao/governance/extensions/GovernorTimelockControl.sol @@ -15,7 +15,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { event TimelockChange(address oldTimelock, address newTimelock); constructor(TimelockController timelockAddress) { - require(address(timelockAddress) != address(0), "timelockAddress: zero address"); + require(address(timelockAddress) != address(0), "zero address"); _updateTimelock(timelockAddress); } @@ -86,7 +86,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes[] memory calldatas, bytes32 descriptionHash ) internal virtual override { - require(!isProposalExecuted[proposalId], "_execute: already executed"); + require(!isProposalExecuted[proposalId], "already executed"); _timelock.executeBatch{ value: msg.value }(targets, values, calldatas, 0, descriptionHash); isProposalExecuted[proposalId] = true; } @@ -115,7 +115,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } function _updateTimelock(TimelockController newTimelock) private { - require(address(newTimelock) != address(0), "updateTimelock: zero address"); + require(address(newTimelock) != address(0), "zero address"); emit TimelockChange(address(_timelock), address(newTimelock)); _timelock = newTimelock; } diff --git a/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol index d6ae31e..25e5939 100644 --- a/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol @@ -54,7 +54,7 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { require(newQuorumNumerator <= quorumDenominator(), "quorumNumerator over quorumDenominator"); - require(newQuorumNumerator >= MINIMUM_QUORUM_NUMERATOR, "quorumNumerator less than Minimum"); + require(newQuorumNumerator >= MINIMUM_QUORUM_NUMERATOR, "less than Minimum"); uint256 oldQuorumNumerator = _quorumNumerator; _quorumNumerator = newQuorumNumerator; diff --git a/contracts/dao/staking/packages/StakingInternals.sol b/contracts/dao/staking/packages/StakingInternals.sol index f10b175..4135563 100644 --- a/contracts/dao/staking/packages/StakingInternals.sol +++ b/contracts/dao/staking/packages/StakingInternals.sol @@ -95,9 +95,7 @@ contract StakingInternals is RewardsInternals { ) internal { User storage userAccount = users[account]; LockedBalance storage updateLock = locks[account][lockId - 1]; - if(updateLock.amountOfToken == 0){ - revert ZeroLocked(lockId); - } + if(totalAmountOfStakedToken == 0){ revert ZeroTotalStakedToken(); } From 60121cdf65c81f76e97b571f64b08c2909eeea3a Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Tue, 14 Mar 2023 05:46:57 +0400 Subject: [PATCH 28/56] Minor fixes of docs, setup, scripts --- .solhintignore | 1 - README.md | 6 +- coralX-scenarios.js | 15 - docs/PROTOCOL.md | 951 +----------------- docs/images/fathom-protocol.drawio.png | Bin 368189 -> 0 bytes package.json | 9 +- .../deployment/5_deploy_governor.js | 2 +- scripts/migrations/test/7_setup_multisig.js | 2 +- scripts/units/setup_council_stakes.js | 6 +- ...ve-bps => set-liquidator-incentive-bps.js} | 0 10 files changed, 15 insertions(+), 977 deletions(-) delete mode 100644 docs/images/fathom-protocol.drawio.png rename scripts/units/stablecoin/collateral-pool-config/{set-liquidator-incentive-bps => set-liquidator-incentive-bps.js} (100%) diff --git a/.solhintignore b/.solhintignore index 93b2df7..e69de29 100644 --- a/.solhintignore +++ b/.solhintignore @@ -1 +0,0 @@ -/contracts/oraclize-api \ No newline at end of file diff --git a/README.md b/README.md index 25325ee..730735a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Fathom project staking, governance and treasury smart contracts for EVM compatib - node v16.4.0 - npm v7.18.1 - CoralX v0.2.0 -- Solidity =0.8.13 (solc) +- Solidity =0.8.16 (solc) - Ganache CLI v6.12.2 (ganache-core: 2.13.2) ## Setup @@ -53,8 +53,8 @@ $ npm run migrate-reset # Deploy to the apothem $ npm run migrate-reset-apothem -# Deploy to the goerli -$ npm run migrate-reset-goerli +# Deploy to the sepolia +$ npm run migrate-reset-sepolia # Deploy to the xdc $ npm run migrate-reset-xdc diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 4c41631..e6f3b18 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -23,77 +23,62 @@ module.exports = { ], transferTokenFromMultisigApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/transfer-tokens.js', '--network', 'apothem'], ], addOwnersToMultisigApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/setup-multisig-owners.js', '--network', 'apothem'], ], addCouncilStakesApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/setup_council_stakes.js', '--network', 'apothem'], ], createDexXDCPoolApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/create_pool_dex_xdc.js', '--network', 'apothem'], ], createDexPoolApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/create_pool_dex.js', '--network', 'apothem'], ], createProxyWalletApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/create_stablecoin_proxy_wallet.js', '--network', 'apothem'], ], openPositionApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/create_stablecoin_open_position.js', '--network', 'apothem'], ], createUpgradeApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/create-upgrade.js', '--network', 'apothem'], ], setupStableSwapApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/stableswap-setup.js', '--network', 'apothem'], ], deployStakingUpgradeApothem: [ - ['compile'], ['execute', '--path', 'scripts/upgrades/1_deploy_staking_upgrade.js', '--network', 'apothem'], ], updateDailySwapLimitApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/stableswap-daily-limit-update.js', '--network', 'apothem'], ], updateMinTokenVotingBalance: [ - ['compile'], ['execute', '--path', 'scripts/units/update-voting-tokens-min-balance.js', '--network', 'apothem'], ], proposeProposalApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/propose-proposal.js', '--network', 'apothem'], ], queueProposalApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/queue-proposal.js', '--network', 'apothem'], ], executeProposalApothem: [ - ['compile'], ['execute', '--path', 'scripts/units/execute-proposal.js', '--network', 'apothem'], ], diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 26c67f7..3702138 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -1,948 +1,5 @@ -# Fathom Protocol -___ +# Protocol Whitepaper +https://gist.github.com/BaldyAsh/3676a18b003758057f634c9af2cfe49a -- [Fathom Protocol](#fathom-protocol) - - [Overview](#overview) - - [1. DAO](#1-dao) - - [2. Votes token](#2-votes-token) - - [3. Governance](#3-governance) - - [3.1. Governance contract](#31-governance-contract) - - [3.2. TimeLock Controller](#32-timelock-controller) - - [3.3. Parameter Change Proposal Flow](#33-parameter-change-proposal-flow) - - [3.4. Collateal Proposal Flow](#34-collateal-proposal-flow) -- [Governance technical specifications](#governance-technical-specifications) - - [Vote Token](#vote-token) - - [Inherited functionality:](#inherited-functionality) - - [Fathom Governor](#fathom-governor) - - [Inherited functionality:](#inherited-functionality-1) - - [Key Functions:](#key-functions) - - [Deployment and initialisation parameters:](#deployment-and-initialisation-parameters) - - [Accesibility Permissions and Roles](#accesibility-permissions-and-roles) - - [4. Staking](#4-staking) - - [4.1. Staking: Main Terms and Definitions, Q&A](#41-staking-main-terms-and-definitions-qa) - - [4.2. Lock To Stake](#42-lock-to-stake) - - [4.3. How Locks Work?](#43-how-locks-work) - - [4.4. How staking works?](#44-how-staking-works) - - [4.5. Release of vFTHM](#45-release-of-vfthm) - - [4.6. Vault](#46-vault) - - [4.7. Rewards](#47-rewards) - - [4.7.1. Rewards: Main Terms and Definitions](#471-rewards-main-terms-and-definitions) - - [4.7.2. Claim Rewards:](#472-claim-rewards) - - [4.8. Streams](#48-streams) - - [4.8.1. Streams: Main Terms and Definitions:](#481-streams-main-terms-and-definitions) - - [4.8.2. How to Propose and Make a stream Live?](#482-how-to-propose-and-make-a-stream-live) - - [4.8.3. Rewards Distribution Schedule:](#483-rewards-distribution-schedule) - - [5 Smart Contract Setup:](#5-smart-contract-setup) - - [How would you deploy the contracts?](#how-would-you-deploy-the-contracts) - - [6. User Flow for Staking](#6-user-flow-for-staking) - - [7. Treasury](#7-treasury) - - [7.1. MultiSigWallet.sol Treasury](#71-multisigwalletsol-treasury) - - [7.1.1. Key MultiSigWallet Functions:](#711-key-multisigwallet-functions) - -Table of contents generated with markdown-toc - - -___ -## Overview - - - -- Fathom Governance Token (FTHM) - the token used in Fathom as a governance token for voting. It is the main stream of rewards in Staking from Treasury. -- vFTHM - the token used in Governance to vote for protocol changes and applying new collateral. It is untransferrable, preventing Sybil attacks (a token holder votes, transfers vFTHM to another account, and votes again). -- DAO - Governance, Staking and Treasury modules to operate core and ecosystem changes voting processes: - - Governance - the proposition and decision-making framework for the different risk parameter changes, improvements, and incentives that constitute the policies and upgrades to Governance itself based on vFTHM. Also, this framework allows the community to use vFTHM to vote for the protocol changes and applying new collateral. The collateral protocol must provide their tokens to the Staking contract to be distributed between stakers. - - Staking - the framework created to increase protocol stability (increase the number of token holders, so that panic attacks do not start and thus not bring down the amount of already funded protocols). Users can stake their FTHM for some time, and during staking, the user gets rewards and vFTHMs. Rewards are represented as streams of FTHM and tokens of the various collateral projects that Governance has voted to integrate. - - Treasury - a platform that holds the required amount of FTHM to be distributed to stakers. -- Bridge - the service that makes assets issued by Fathom for one ledger transferrable to another ledger. - -___ -## 1. DAO - - - -Governance - the proposition and decision-making framework for the different risk parameter changes, improvements, and incentives that constitute the policies and upgrades to Governance itself based on vFTHM token. - -vFTHM follows the VeNomics model - vote escrow. Here users lock FTHM and receive intransferable vFTHM tokens in exchange. Voting rights and staking rewards are granted according to the amount of vFTHM tokens that each user holds. The locking period can be between a 0 time and five years. The longer the lock, the higher the rewards and voting power. - -Also, this framework allows the community to use vFTHM to vote for the projects for DAC they want to be used as collateral in Lending. - -The supported protocol must provide their tokens to the Staking contract to be distributed between stakers. - -Funding is possible from Treasury Smart Contract (Contributors reserve). - -Governance also works for cross-chain Bridge protocol. - -___ -## 2. Votes token - -Ve tokens causes positive price pressure on the FTHM token because time locking a token increases the long-term commitment from the holders, reduces circulating supply, removing potential downward price pressure. -Token locking reduces risk of flash loan attacks on our protocol. Users cannot allocate funds to themselves if the purchase enough of our vote token using a flashloan. -A single staking contract can be used for both staking and vote power calculations. - -vFTHM token functionality: -- In-transferable -- ERC20 Votes -- Delegate (Auto delegate on transfer and mint to yourself) - - -___ -## 3. Governance - -Fathom Governance uses a simple vote structure that prevents ambiguity and the illusion of choice. -(See Alpacas governance for an example of an illusion of choice: -https://snapshot.org/#/alpacafinance.eth/proposal/0x388adc3eecab51a667fc6e972fc28a99cf3f04dbc24945e4369a5ebd9d07a8c6) - -**LifeCycle of a Proposal:** - -An entity creating a Proposal must have at least _N_ vFTHM tokens. This is called the Proposal Threshold and prevents spam creation of proposals. - -Once a proposal has been submitted a delay starts. - -Delay is set to two days. The delay allows voters to acquire and lock more FTHM if they wish to have more voting power. Once the delay period is over, voting weights are finalized for the current proposal. - -The voting period is set to two weeks, starting immediately after the end of the delay period. - -Vote options: -- 0 = Against, -- 1 = For, -- 2 = Abstain - -If vFTHM holders vote in favor of the proposal, the execution of the proposal will go into a timelock of two days. This gives users the time to exit if they disagree with the proposal and gives us time to veto the proposal if we deem it malicious. - -Contracts can have Governor contract as their ownerr, allowing governance to change permitted parameters. - -Treasury controls release of funds to protocols seeking funding, and Staking controls the calculation of staking rewards and voting power of FTHM users. - -___ -### 3.1. Governance contract -This component is responsible for the creation and execution of on chain governance proposals. Key features and flow: -* The voting power of each account in our governance model is determined by how much *vFTHM* - our native vote token - each account holds. -* Holders of vFTHM with a minimum balance of 1000 may submit proposals to be voted on. -* vFTHM token holders have three options to vote on a proposal: For, against and abstain. -* A quorum of 4% of all voting power is needed for a voting period to be considered valid. -* Once a vote is complete and more than 50% of the voters voted in favour of the proposal, it is added to the proposal queue. -* Proposals in the proposal queue are delayed by one week before execution, and the governance signers need to confirm the proposal before it can be executed. - -In order to protect our protocol, the following governance precautions have been taken: -1. Flashloan attacks: - * Voting power for each account is calculated using the balance of vFTHM with a one block delay. This is to prevent potential attackers from accruing voting power, creating a proposal and voting within the same block. - * Delayed execution and propose: delay of n blocks is imposed before a proposal can be executed or voted on. - -2. Ambiguous proposals and the illusion of choice: - * Voting options for proposals are limited to *For, Against and Abstain*. This prevents situations where it appears that there is only the option to vote for the least detrimental option. For example, a malicious proposal could have the options: *Increase emissions by 5%, 10% or 20%*. Here there is no option to vote against the proposal. - -___ -### 3.2. TimeLock Controller -TimeLockController contract is inherited by the MainTokenGovernor contract. Once a proposal has successfully passed the voting process, the execution of the proposal is handled by functionality from the TimeLock Controller. Functions inherited include the queue and execute functions detailed in the proposal flow below sections. - -___ -### 3.3. Parameter Change Proposal Flow -Here we cover the implementation of governance functions and show the important function calls. - -Consider a scenario in which a *proposer*, someone who is making a proposal, wants to use the voting process to impose a change to a variable in a contract that the governor contract has control over. In this case, let the variable in question be the uint value in the dummy contract Box.sol which is owned by the governor contract. - -First a function call that the proposer wants to execute through governance is encoded: - -``` -encoded_function = web3.eth.abi.encodeFunctionCall({ - name: 'store', - type: 'function', - inputs: [{ - type: 'uint256', - name: 'value' - }] -}, [**Some new integer value**]); -``` -A proposal is then contstructed and submitted to the Governor contract using the propose function: -``` -mainTokenGovernor.propose( - [box.address], - [0], - [encoded_function], - PROPOSAL_DESCRIPTION, - {"from": **Proposers Address**} -), -``` -A Proposal Id for the above proposal is returned in order to keep track of the proposal. - -If the proposer account has at least minimum number of vote tokens required to create a proposal then the status of the proposal will be Pending. See ProposalState enum below. This is because there is a block delay of one block from between when a proposal is successfully submitted and when it becomes active for voting. - -``` -enum ProposalState { - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed -} -``` - -After the delay period, using the relevant proposal Id voters can now vote on the proposal using the following function call: -``` -mainTokenGovernor.castVote(proposalId, "1", {"from": **Voters Address**}); -``` - -If the majority of voters vote in favour of the new proposal, then it is ready to be queued in the TimeLock contract for execution. Here a delay is imposed so that protocol users can safely exit the protocol if they do not agree with the proposal. -``` - mainTokenGovernor.queue( - [box.address], - [0], - [encoded_function], - description_hash, - {"from": **Any Address**} - ); -``` - -One key difference between the queue function call and the propose function call is that the description is hashed in the input of the second call. - -Another safety element included in our protocol is the multiSig vito option. In order for a queued proposal to be executed, addresses who have been granted signer roles need to confirm the proposal. The following function call is used for this confirmation: -``` -mainTokenGovernor.confirmProposal(proposalId2, {"from": **Some Governance Owners Address**}); -``` - -It is expected that there would have to be many of these approvals from different signer accounts. In order for a proposal to be executed the minimum amount of signers should confirm the proposal. - -Once the timeLock queuing period is over and if enough signers have confirmed the proposal, the proposal is ready for execution: -``` - mainTokenGovernor.execute( - [box.address], - [0], - [encoded_function], - description_hash, - {"from": **Anny Address**} - ); -``` - -Now, the encoded function call should have been execueted. Upon inspection, the value parameter in Box.sol should be updated to the value proposed. - -### 3.4. Collateal Proposal Flow - -Consider a scenario in which a proposer creates a proposal to request a new collateral creation that requires asset to be tokenized, put in Staking, DEX and Stablecoin. This scenatirio is similar to *Parameter Change Proposal Flow* above. The main difference is that there are now three encoded function calls: -- Tokenize asset -- Create DEX pool with FXD -- Create Staking stream -- Create FXD pool - -These functions is to be called by the MultiSigWallet treasury and thus requires MultiSigWallet treasury confirmation. -``` -encoded_transfer_function = web3.eth.abi.encodeFunctionCall({ - name: 'collateralize', - type: 'function', - inputs: [{ - type: 'address', - name: 'owner' - },{ - type: 'string', - name: 'name' - },{ - type: 'string', - name: 'symbol' - },{ - type: 'uint256', - name: 'totalSupply' - },{ - type: 'int256', - name: 'initialPrice' - }] -}, [ - **Proposers Address**, - COLLATERAL_NAME, - COLLATERAL_SYMBOL, - COLLATERAL_TOTAL_SUPPLY, - COLLATERAL_PRICE -]); -``` -In order for a potential transaction to be confirmed by the MultiSigWallet treasury, a sumbission containing all of the details for the transaction needs to be submitted to the MultiSigWallet treasury. The following encoded function call is the submission of the above function call to the MultiSigWallet treasury for review to be confirmed. - -``` -encoded_treasury_function = web3.eth.abi.encodeFunctionCall({ - name: 'submitTransaction', - type: 'function', - inputs: [{ - type: 'address', - name: '_to' - },{ - type: 'uint256', - name: '_value' - },{ - type: 'bytes', - name: '_data' - }] -}, [mainToken.address, EMPTY_BYTES, encoded_transfer_function]); -``` - -Only the governance contract and the MultiSigWallet contract owners can create such a submission. In this scenario, the proposer is not a MultiSigWallet owner so in order to submit the transaction, it needs to go through governance. The proposer creates and submits a proposal, in the same way as *Parameter Change Proposal Flow* and if the proposal is successfully voted on and executed, then the transaction is submitted to the MultiSigWallet treasury. - -The final guard against funds leaving the MultiSigWallet treasury are the signers confirmations. Once the following funciton has been called by enough MultiSigWallet owners, then the transaction of VC funds from MultiSigTreasury to the proposer is executed. - -``` -MultiSigWallet.confirmTransaction(uint _txIndex, {"from": **Some MultiSigWallet Owners address**}) -``` - - -# Governance technical specifications - - -## Vote Token -vFATHOM (vFTHM) - -#### Inherited functionality: - -vFTHM is a Non-Transferable, Burnable, Pausable and Mintable ERC20 token with access control. - -**ERC20Permit.sol:** -"Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]" -Inherited ERC20Permit functionality **allows** for the delegation of voting power by signature. - -**ERC20Votes.sol:** -"Extension of ERC20 to support Compound-like voting and delegation" -Inherited ERC20Votes functionality **accounts** for the delegation of voting power by signature. - - - -## Fathom Governor - -Currently called MainTokenGovernor.sol - -#### Inherited functionality: - -**Governor.sol:** -The main functionality comes from: Governor.sol, further discussed in the section "Key Functions". - -**GovernorVotes.sol:** -Inherited functionality responsible for calculating voters voting power given an account and a blocknumber. - -**GovernorSettings.sol:** -Parameters controlling governance are from: GovernorSettings.sol. The parameters of interest are: - - uint256 private _votingDelay; - uint256 private _votingPeriod; - uint256 private _proposalThreshold; - -Here: -_votingDelay is the delay (in number of blocks) since the proposal is submitted until voting power is fixed and voting starts. -_votingPeriod is the number of blocks that an active proposal remains open for voting. -_proposalThreshold is the number of vFTHM tokens required in order for a voter to become a proposer - - -**GovernorVotesQuorumFraction.sol:** -The quorum is the fraction vFTHM token holders who cast a vote as a fraction of the total voting power (total supply of vote tokens) required for a proposal to pass. Inherited functionality responible for setting the quorum requirements as a fraction of the total token supply. - -**GovernorTimelockControl.sol:** -Timelock extensions add a delay for governance decisions to be executed. Functionality inherited from GovernorTimelockControl.sol are responsible for controlling the queing and execution of proposals. - -**GovernorCountingSimple.sol:** -There are three options when casting a vote: - - enum VoteType { - Against, - For, - Abstain - } - -Votes for each proposal are counted and stored in s struct with the form: - - struct ProposalVote { - uint256 againstVotes; - uint256 forVotes; - uint256 abstainVotes; - mapping(address => bool) hasVoted; - } - -Functions for tallying votes and determining proposal success are inherited from GovernorCountingSimple.sol. - - -#### Key Functions: - -***propose:*** - - /** - * @dev Create a new proposal. Vote start {IGovernor-votingDelay} - blocks after the proposal is created and ends - * {IGovernor-votingPeriod} blocks after the voting starts. - * - * Emits a {ProposalCreated} event. - */ - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public virtual returns (uint256 proposalId); - -***state:*** - - /** - * @dev Current state of a proposal, following Compound's convention - */ - function state(uint256 proposalId) public view virtual returns (ProposalState); - -Here ProposalState is the enum: - - enum ProposalState { - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed - } - -***getVotes:*** - - /** - * @notice module:reputation - * @dev Voting power of an `account` at a specific `blockNumber`. - * - * Note: this can be implemented in a number of ways, for - example by reading the delegated balance from one (or - * multiple), {ERC20Votes} tokens. - */ - function getVotes(address account, uint256 blockNumber) - public view virtual returns (uint256); - -***castVote:*** - - - - /** - * @dev Cast a vote - * - * Emits a {VoteCast} event. - */ - function castVote(uint256 proposalId, uint8 support) - public virtual returns (uint256 balance); - -and: - - /** - * @dev Cast a vote with a reason - * - * Emits a {VoteCast} event. - */ - function castVoteWithReason( - uint256 proposalId, - uint8 support, - string calldata reason - ) public virtual returns (uint256 balance); - -For delegatee votes: - - /** - * @dev Cast a vote using the user's cryptographic signature. - * - * Emits a {VoteCast} event. - */ - function castVoteBySig( - uint256 proposalId, - uint8 support, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual returns (uint256 balance); - -For all forms of voting, the voting options are from the following enum. - - // enum VoteType { - // Against, - // For, - // Abstain - // } - // => 0 = Against, 1 = For, 2 = Abstain - -***queue:*** - - /** - * @dev Function to queue a proposal to the timelock. - */ - function queue( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public virtual returns (uint256 proposalId); - -***confirmProposal:*** - -The governance process and all of its contracts are owned by N wallets. A predetermined subset of confirm a proposal before it can be executed. This is exlpained in more detail in the "Accesibility Permissions and Roles" section. - - function confirmProposal(uint _proposalId) - - -***execute:*** - - /** - * @dev Execute a successful proposal. - This requires the quorum to be reached, - the vote to be successful, and the - deadline to be reached. - * - * Emits a {ProposalExecuted} event. - * - * Note: some module can modify the requirements for - execution, for example by adding an additional timelock. - */ - function execute( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public payable virtual returns (uint256 proposalId); - -#### Deployment and initialisation parameters: - -Steps: -1. Deploy Timelock Controller contract -2. Deploy vFTHM token contract -3. Initialize the Timelock Controller contract with parameters: - uint minDelay, address[] proposers, address[] executors -4. Deploy the FathomGovernor contract (Currently called MainTokenGovernor) with parameters: IVotes _token, - TimelockController _timelock, - address[] memory _signers, - uint _numConfirmationsRequired - -The signers arrays is discussed in the "Accesibility Permissions and Roles" section. - - - - - -## Accesibility Permissions and Roles - -***FathomGovernor and TimelockController:*** -After deployment of FathomGovernor and TimelockController, the following TimelockController permissions need to be given to FathomGovernor: - - timelockController.PROPOSER_ROLE() - timelockController.EXECUTOR_ROLE() - timelockController.TIMELOCK_ADMIN_ROLE() - -***Signers:*** -Both Multisig and FathomGovernor have arrays of signers. The signers have similar functionality, but differ in their responsibility. - -The MultiSig signers are responsible for the confirmation of transactions queued in the multisig wallet, whereas the FathomGovernor signers are responsible for the confirmation of a proposal before it may be executed. - - - - -___ -## 4. Staking - -Governance Tokens (vFTHM) are acquired by staking FTHM. Length of staking period is proportional to how many governance tokens one receives. Staking is essentially voting escrow. - -Staking vault functionality: -- Distribute other protocols tokens to stakers depending on rewards calculation. -- Hold FTHM tokens. -- Distribute vFTHM tokens according to calculation. - -___ -### 4.1. Staking: Main Terms and Definitions, Q&A - -**Lock Position** - The positions that a user stakes for which rewards are generated. The Lock positions are created to incentivize people to participate more frequenty in the protocol and it provides more granular control. Furthermore, it eases the calculation of vFTHM. - -**Why Lock Tokens for a certain period?** - -Calculation of release of **N** vFTHM tokens is based upon lock period. -FTHM locked for certain period incentivizes people to participate in the project more. - -**Why multiple Lock Positions?** - -Helps for flexible staking with multiple lock periods, allowing people to close undesirable positions earlier while retaining others. -Assists in easier calculation of the number of vFTHM tokens to release - -**Auto Compounding Shares** - The FTHM Token Shares awarded to the users are used to facilitate auto compounding feature. This feature allows new FTHM Tokens Rewards to be added to the pool that proportionally increases the rewards of the stakers and automatically performs auto-compounding without need of restaking the FTHM Tokens received as rewards. - -**Weighted Shares** - The amount of stream shares a person gets is based upon weighing function that decreases the amount of stream shares a person gets depending upon when in the timeline of the project a user stakes. - -Eg: Suppose the project gets launched on a certain date, lets say 1st January 2023. Now a person stakes their tokens close to start date, for example: 25th January 2023. This staker has more proportional shares of rewards than a person that stakes around 25th August 2023. This is due to the linearly decreasing weighted shares during the timeline of the project - -**Why decreasing weighing function?** - -The weighing function and its mechanics helps to incentivize early staking and long term holding. - -**Release of Governance Token** - The governance tokens are released based upon amount of staked FTHM Tokens and how long they are locked for. - -**What is distribution mechanism?** - -Distribution mechanism will allow multiple protocols distributing rewards in streams based on shares with weighting function. - -It incentivizes different protocols to use FTHM, by allocating some funding to the protocols and in turn they distribute their tokens back to the community. - -**Rewards Technicals** - -Stream Shares: Shares for streams of each protocols users get for rewards calculation. - -Weighing Function: The amount of stream shares you receive is based upon weighing function, which linearly decreases the amount of shares you receive when you stake as time passes by. - -The user is able to unstake after the locking time period, however they are incentivized to keep staking, because the weighting function is reapplied to all the shares for staked FTHM tokens, for each unstaking activity. - -Example: A user stakes 10 FTHM Token for 5 years. Now, suppose his stake generates 100 TRI as rewards in 5 years. His Lock period expires. He is free to unstake. If he unstakes 5 FTHM Tokens only, he would get 50 TRI rewards. But the rewards for remaining 5 FTHM tokens would not be 50 TRI but would decrease to ~12.5 TRI, due to the weighing function being reapplied after each unstaking. - -___ -### 4.2. Lock To Stake - -Locking your FTHM Token is the only way you can stake and get rewards. Locking can be done by creating lock positions. Locking stakes your token in the total pool of tokens and the contract calculates the shares for rewards. Though you can early unlock, it is highly disincentivized through penalties. - -The main interface for Locking: IStakingHandler.sol. - -___ -### 4.3. How Locks Work? - -External Functions: - -Contract: StakingHandler.sol -The Flow: - -* When creating a lock position the user needs to specify the amount to lock and how long you want to lock it for (unlockTime). -``` - /** - * @dev Creates a new lock position with lock period of unlock time - * @param amount the amount for a lock position - * @param unlockTime the locking period - */ - function createLock(uint256 amount, uint256 unlockTime) external; -``` -* The lock position is identified by a lock Id, and starts accumulating rewards -* Once the lock position expires, the user can call unlock function with lockId to completely unlock his position. -``` - /** - * @dev This function unlocks the whole position of the lock id. - * @notice stakeValue is calcuated to balance the shares calculation - * @param lockId The lockId to unlock completely - */ - function unlock(uint256 lockId) external; - -``` - -* You can early unlock before your locking period expires but you will be penalized. The penalization is proportional to the remaining time. Note: If you early unlock, your vFTHM balance will be reduced by the number of vFTHM previously released for the lock position. - -``` - /** - * @dev This funciton allows for earlier withdrawal but with penalty - * @param lockId The lock id to unlock early - */ - function earlyUnlock(uint256 lockId) external; -``` - -___ -### 4.4. How staking works? - -The Flow: - -* Staking a lock position increases the Total amount of staked FTHM Token and Total FTHM Token shares -> -``` - /** - * @dev Stakes the whole lock position and calculates Stream Shares and Main Token Shares - for the lock position to distribute rewards based on it - * @notice the amount of stream shares you receive decreases from 100% to 25% - * @notice the amount of stream shares you receive depends upon when in the timeline you have staked - */ - function _stake( - address account, - uint256 amount, - uint256 nVeMAINTkn, - uint256 lockId - ) internal -``` -* A FTHM position stream shares, FTHM Token Shares and FTHM Token Balance for the lock position is calculated. This stream shares is multiplied with rewards per shares during each claim of rewards for the lock. -* The unstaking sets the position stream shares,FTHM Token Shares and FTHM Token Balance of a lock to zero. -``` - /** - * @dev Unstakes the amount that you want to unstake and reapplies the shares to remaining stake value - */ - function _unstake( - uint256 amount, - LockedBalance storage updateLock, - uint256 stakeValue, - uint256 lockId, - address account - ) internal - -``` -* Total amount of staked FTHM Token and Total FTHM Token shares are also decreased - -___ -### 4.5. Release of vFTHM - -The release of vFTHM is based upon locking vote weight which is set such that the vFTHM get released in this way: - -* Lock 1 FTHM Token locked for 1 year = 1 vFTHM Released -* Lock 2 FTHM Token locked for 6 months = 1 vFTHM Released -* Lock 4 FTHM Token locked for 3 months = 1 vFTHM Released - -Basic formula for calculation of vFTHM: - -nVOTETokens = amountLocked * lockingPeriod / lockingVoteWeight - -Here, lockingVoteWeight is applied so that 1 FTHM Token locked for 1 year releases 1 vFTHM - -___ -### 4.6. Vault - -What is Vault? - -Vault is the smart contract that actually stores all the tokens for transfers. It consists of the reward tokens and FTHM Tokens and acts as a intermediary to transfer tokens from the staking contract to the stakers. - -``` - // This function is used for the transfer - function payRewards( - address _user, - address _token, - uint256 _amount - ) external - - // Whitelists a token to pay as rewards - function addSupportedToken(address _token) - external; -``` - -___ -### 4.7. Rewards - -This Component is used for calculating the stream rewards for the locked positions. - -___ -#### 4.7.1. Rewards: Main Terms and Definitions - -**Rewards Per Share** : Each stream has it's own rewards per share. - -**Reward amount** : Locked Positions Stream Shares * (Current Rewards Per Share - Rewards Per Share During Last Claim) - -**Rewards Distribution Schedule** The flexible rewards timetable that protocols can set to distribute rewards. - -___ -#### 4.7.2. Claim Rewards: - -The Flow: - -* A User stakes and creates a lock with lockId and rewards gets accumulated. -* Rewards is available to be claimed using the function claimRewards(). The claimRewards() function calls moveRewardsToPending function which makes the rewards available for withdraw. -``` - /** - * @dev This function claims rewards of a stream for a lock position and adds to pending of user. - */ - function claimRewards(uint256 streamId, uint256 lockId) external - - /** - * @dev This function claims all the rewards for lock position and adds to pending of user. - */ - function claimAllRewards(uint256 lockId) external - - /** - * @dev move all the streams rewards for a user to the pending tokens - * @param account is the staker address - * @param lockId the lock id of the lock position to move rewards - */ - function _moveAllRewardsToPending(address account, uint256 lockId) internal - -``` -* After claiming rewards a staker can withdrawStream() their rewards. -``` - /** - * @dev withdraw amount in the pending pool. - */ - function withdrawStream(uint256 streamId) external override -``` - ---- -### 4.8. Streams - - -Streams basically means streaming rewards in timely manner to the protocol users. - -___ -#### 4.8.1. Streams: Main Terms and Definitions: - -**Stream Owner**: Stream owner is the account that actually makes the streaming of the protocol tokens live. It is set during proposing of a stream. Note: This account usually should belong to an account owned by a member of the protocol seeking funding. - -**Stream Manager**: Stream Manager is the account that is able to propose a stream and set all the parameters. This account set all the params properly, and the account can belong to a member of our team. - -**scheduleTimes** timestamp denoting the start of each scheduled interval. Last element is the end of the stream. - -**scheduleRewards** Rewards to be delivered at each scheduled time. Last element is always zero. - -___ -#### 4.8.2. How to Propose and Make a stream Live? - -The Flow: - -1. The Stream Manager or the Staking Admin of the staking contract can whitelist (propose) a stream. Whitelisting of the stream provides the option for the stream owner to deposit some tokens on the staking contract and potentially get in return some tokens immediately as a funding. - -``` -function proposeStream( - address streamOwner, - address rewardToken, - uint256 mainTknDepositAmount, - uint256 maxDepositAmount, - uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, - uint256 tau - ) external -``` - -2. The stream owner is then able to make the stream live by calling createStream() function.Note: Stream must have been successfuly proposed before making it live. - -``` -function createStream( - uint256 streamId, - uint256 rewardTokenAmount - ) external -``` - -___ -#### 4.8.3. Rewards Distribution Schedule: - -The Flow: - -1. The rewards distribution schedule for each stream is different. The rewards distribtution is first setup with discussions from protocols seeking to stream their rewards. Then a stream is proposed and created. - -``` -* @param time timestamp denoting the start of each scheduled interval. Last element is the end of the stream. -* @param scheduleRewards remaining rewards to be delivered at the beginning of each scheduled interval. Last element is always zero. - -struct Schedule { - uint256[] time; - uint256[] reward; -} -``` - -2. Once the stream is made live through createStream, the rewards are calculated. - -``` -function _getRewardsAmount(uint256 streamId, uint256 lastUpdate) internal view returns (uint256) -``` - ---- -## 5 Smart Contract Setup: - -1. The top most contract is StakingPackage.sol which inherits StakingHandler.sol and StakingGetter.sol. Note: This contract does not have any logic -2. The most important Contract where all the internal functions are called is StakingHandler.sol -3. There are StakingInternals.sol and RewardsInternals.sol that has all the internal functions required for staking and rewards calculation - -### How would you deploy the contracts? -1. First the StakingPackage.sol is deployed -2. Next step is to deploy VaultPackage.sol. -3. Then after deploying all the tokens, then you setup FTHM token addresses and vote token addresses for the staking contract. -4. The initializeStaking can be called only once and will initialize all the addresses and - -``` - /** - * @dev initialize the contract and deploys the first stream of rewards(FTHM) - * @dev initializable only once due to stakingInitialised flag - * @notice By calling this function, the deployer of this contract must - * make sure that the FTHM Rewards amount was deposited to the treasury contract - * before initializing of the default FTHM Stream - * @param _vault The Vault address to store FTHM and rewards tokens - * @param _fthmToken token contract address - * @param _weight Weighting coefficient for shares and penalties - * @param streamOwner the owner and manager of the FTHM stream - * @param scheduleTimes init schedules times - * @param scheduleRewards init schedule rewards - * @param tau release time constant per stream - * @param _voteShareCoef the weight of vote tokens during shares distribution. - Should be passed in proportion of 1000. ie, if you want weight of 2, have to pass 2000 - * @param _voteLockWeight the weight that determines the amount of vote tokens to release - */ - function initializeStaking( - address _vault, - address _fthmToken, - address _voteToken, - Weight memory _weight, - address streamOwner, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, - uint256 tau, - uint256 _voteShareCoef, - uint256 _voteLockWeight, - uint256 _maxLocks - ) -``` - -## 6. User Flow for Staking - -Staking is a method for generating yield and accessing voting power by depositing your FTHM for a certain time-period. Staking functions similarly to typical bank interest accounts. - -This module allows users to stake their FTHM and earn rewards. Rewards comprise of “Stream rewards” which are native tokens provided by the protocols that want to collaborate with us, which we distribute as stream back to the community of stakers to incentivize people to stake in our protocol. Staking your FTHM token for certain lock period also earns you voting power on the DAO. This voting power is represented by the vFTHM token that you receive depending on the amount of FTHM and locking period of your staked position. - -The procedures for creating lock position: - -1. Click on the “Staking” tab from the side menu to view staking options. Then select “Locking”, this will open the staking module. -2. Enter the staking amount in the “Stake Position” text field. This represents the amount of FTHM token to be staked. -3. Select Lock period by using the slider bar. Maximum lock period that can be selected is up-to 365 Days. -4. Then click on “Create Lock” option. This will prompt you to sign two Metamask Transactions. First is for approving the staked position amount of FTHM token and the second is for creating the lock position. -5. Once both the transactions are confirmed the new lock position is created and can be viewed at the last index of the lock positions table. -6. To unlock your staked fathom there are two options as “Early Unlock” and “Unlock”. Early Unlock can be performed if the stake time period is not exhausted. -7. “Unlock” is only activated once the stake time-period is exhausted. -8. Both “Early Unlock” and “Unlock” will prompt you to sign two Metamask Transactions. First one is to claim all the remaining stream rewards for the lock position and second is to perform the actual unlocking of the position -9. Clicking on claim stream rewards allows you to claim all the accumulated stream rewards for all the existing positions. -10. Withdrawal of all stream rewards and unlocked fathom can be completed by clicking on “Withdraw all rewards and Remaining Unlocked Fathom” button - -This Component is used to lock and create different FTHM Token's Lock Positions that generates staking rewards. - -Staking incentivizes different protocols and users to use our FTHM token. Our staking protocol encompasses different protocols that want funding. The Governance allocates some funding in Stablecoins and FTHM Tokens to the Protocols seeking funding. In return of funding, the protocols colloborating with us provide us their native tokens which we stream back to the staking community as an incentive to use our protocol. - - -___ -## 7. Treasury - -Multisig wallet holds the funds. -Held to be distributed to other protocols seeking funding, or updates within our protocol that require funding. -Penalties from early withdrawl and liquidations are deposited here. - -Although the ultimate goal is decentralisation, control over the funds to be distributed by our protocol is essential. This is especially true while the protocol is new. - -Features: - -- Multisig. -- Holds funds to be distributed to other protocols seeking funding, or updates within our protocol that require funding. -- Penalties from early withdrawl and liquidations are deposited here. - -___ -### 7.1. MultiSigWallet.sol Treasury -This contract is responsible for the distribution and protection of the protocols funds. Key features and flow: -* Fees such as liquidation fees are deposited to accrue in the treasury. -* In order to transfer funds from the wallet to another address, a transaction needs to be: - 1. Submitted - 2. Confirmed - 3. Executed -* There are multiple owners of this contract, and each of them can submit or confirm a transaction. -* The main governance contract -Detailed in the next seciton.- can also submit a transaction but it cannot confirm it. -* A threshold is set for the minimum amount of owners to confirm a transaction for it to be executed. -* Once the above threshold has been met, any address can execute the transaction. - -___ -#### 7.1.1. Key MultiSigWallet Functions: -Adds a new transaction to the transaction mapping, if transaction does not exist yet. The parameter _to is the Transaction target address. The parameter _value is the transaction ether value. The parameter _data is the Transaction data payload. -A succesful submitTransaction call emits SubmitTransaction event. -``` - function submitTransaction( - address _to, - uint _value, - bytes memory _data - ) -``` -The following function allows multiSig owners to confirm a transaction. Parameter _txIndex is the transaction Index. It emits the event ExecuteTransaction. - -``` - function confirmTransaction(uint _txIndex) -``` - -The followning function allows anyone to execute a confirmed transaction given the parameter _txIndex, the transaction Index. - -``` - function executeTransaction(uint _txIndex) -``` +# DAO system design +![DAO-SYSTEM-DESIGN](https://i.imgur.com/X7ylbog.png) diff --git a/docs/images/fathom-protocol.drawio.png b/docs/images/fathom-protocol.drawio.png deleted file mode 100644 index 3820851b0a0d5eb735d78c419761a162b7b60c96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 368189 zcmeFZcQ}^s|38jMQBjFxmLyrp%DziOnaSQID=T}`U1{(>* zz4<*aZq-Y#&*%O9{r>qK-{W|99CF>a>pHLVe4fw!d_2!bit-nClN}->ARyR%N&1`; z0m05X0)p*MJGR3&&%zr|5fJP;V=5)3cu7i%S<%MI$kg1BfI#|Dpc<+A)i2Z$TF;)M zObChJ-0L9T%TD;_UNw<4&z;zNM^VQQZ@=@HK|}c#SM0+(yLAp+8Ri$gZhXbOiH7_{ zd6xPI4l5;8qkE6*l)G(T{@Tlhsu0`0j?8VhTXy#`G04C+!A?%=>a@-aHow*B@qvIi{kq^Oxw zKL<$K>|$TFP$Wk^Wuqj*TzJqV%ElHUq+uW?3t z#~)uHpFG$6l6K8lWYzJ@m!Mq>AMUBJO3~AEh4WRk;^NyB*;UaW6 zB3^WyRcaqSTgJuRADua0@L{$kv-ov0ktGurp+60epCpM4J+|Y9)GoChB05j2e7)3Z zy3Q}>+&<`qJ7#T}`iZ9H=`kbT1Ixm`>b+`%LwQFzeBV`jh8G%hO!L-%deTJvkexD& zW=GfO!>V3Qhj}=TDPQADJbCwk#0{39b28mR4^D^j?3xT1G9WSSDlqYKHk+eG7RITmgwnnhfmTc^HP5a5&zPrae zc{C-cV;-ik9GLycE-5Nq9(H#4r5a__LGRMO&m`=PrQgmi_$N=Ep7A zHqP%9{U{={6J;bJsOp-=CepE=;^^DkikKP3(YbeM5BW0>?fqo7>o~FJ*&4FW;o7y^%|jvRC8#&LUxEsvDnOADKVD)ubinw2QV-GRIcG=aUomTHx%O ztH;BY%9Drpztuk~#W-+NDyWFywIuJ0gVL#mGuBpZ`zh|K>8Xd`+CD15?>9DkkL{lP zC#g^NXN7(=P@VCgk2zj?*D{tVw(R_%UuFTf|`yD6F5+dJ_;}O)%UsW%TOBn!C^R-iXthoG^JkPCzmF*0(ITq<{8z;PTcp@cr7JatIYuD^bW)^3Oth^^m6j@1a!*HP{oId;|!P8Nm$*RV% zK{#4IC>Se;)jT421g`Yb!pLP=r+ulv`oN}xTCCu3KQ8L`D_i_=5&7NT09pfCY zws-Q2>l0Vp7I^8m{}xF^0S)nON=m}(XO6ts6;*Y6NPE{?w1&RKaY93VVfEb&Xdiv@ zCPIh1E113XglvVhxAtmpZ!Bc^=;Bg9UQN+~uG(?uDdmZ?Qqz{AkHS|#6jDM6OBGKn{|!jE2F6!F(8+xCXW*~jfHuQEyf+1*!U z?D)%3AtuKUdCQ;uR`br}u;vr)^BgsY>}XnUN1Wq%-B@EeO(JreP?q*vq3lW5C+Or4 zJ2-dBd@gFDkEe<&xcR{)oe`%MaqR(j9?-U*G3Aik6b}3uB^pcvi_#2AL<(Eq? z%U?!czI6F)^pR+?=&l8V}t1QZ@vS~dPFWY;A(RsTwtusfam)oN@sa4q;jQW+F zl(1({3vzkv61jLL@a-U`T(#$2rAmU5g<|gOMJ1njqgN%$ zVezW5s;?YSBhNG=MKs!#!lNs%L`LaGcf@x@OJL4?I~cY1m48(7IfIxIXCLFxqjEAh z0=WpIyd!NZJ zy#31m+S6BMw-SD^f0kfjV9|?`jZ(Q#`ozV@w4|eis9U9^)F<*=+JLN!VD7Vy3u8jO zCwB_^zc3dZIJsbc)9{e7=8de<=?L25C)a|E(#zxP<2&1$+;SE;r$nY&sc(?qKYzdY zkT;fkj4Oe2KPQc*pr!=2Hto9B0nK`i_O!Q}OX><~iOG3JQmNOPZZyYdWSV=oWHlKz zsW$7TNR4QZ5IYQwh+N!zA=7W4?|a&M!=&p~sXQN&u%6#B-ITqGKdbx4ZBiHd-_&qi zh-6s7)fDr#TE}&V^M)FnEur!;rJZW|((q_VxyR0e)871hQ>H;!NuEu*ZLg@+t-OJ= zwzOj@)zS4madBaBO=%rzjkq?h;)Iq2?^neSZ*X3FY0LI;DYMF6vTL>Gb3IF^L4K)T z#B78{gs6C=5B5+gQ-*yT3(r*^-$tV(ORFr$$8*QVESdH*`6~ESN$K(FMT`k{#>mnc z9kZpGijItmY#Mvo=^8|8COC9YvX|+n-eBim3@s(i2k!447*l;0eI0$zr!v(QnEi$K zzE^)gP^e=rB^a#pb(F%IJcOZ^`u)B``?I*Guf4E%aXQ{mOhn?wa`}YgXRc3PKYaEw z-(g5T)usDJl=PO0AWwGyL*=h`48=sH#c~va?sF`+(yMf0+V>tZu(CJIsW;vQ} zHf|QnOT=sUChv{zjAE;ZxW#)#^2*bu=jSdzR%i+0Kgp}#HTX#U3-=e+FG9yO}H>9eOPd_ClBcdmY>l{Sn~jhVI8XcK6o5!359q07_htyk$8 z+8d}EG0WM`;cn<+bzkAa`3sS^S=HH`0%>1n@=m#{o@8BjxLrlW%9~L1|tS}?PuydtYe+D7@rBTC$a01ua;%V#Ixp0Gg3LxC?9ZpK`l8_ zVCOUNX<&rU+?X?BBK72l@r;mp+0lvyjbqr}DwmUC!md;FH{=IjHe?#vdiTv%kcVa% zNEmuFEO&a-dzX86a7(LKE19c{CC%6HoXM{Xj177ylSS>q-S%;Z-cj|sWT)Y-H1GVt z$;&*?g@aU{yK8*XvH8)+o3FYm@{!-dwDeS?Azje^VDjKj^&i;l7c(z7Ut5R@>{b(u zf1PwfXU1XXd3_(pOAf)rSMmPw1Btuh%r(vO92Q!5hE6r;Bu&P(Tm=R5adE|^W08Z- zO{mU_zfC=T;@h#pw_#y-1%>L=8T z<=Ugl^qS#>1?rFy|Fn=1zk?8~puOM&{)}{|bb68DTTV-R16WiprulJ;UJzDwGaq;Q zF@fIydcQl{HwB5C;k7P(b@#3|w!Cf;k$(^&;dpGk;vKdETUzBDE^5=!?KVYH;#K0E zNJH16(?YQ_I=S>}PowXB8Oz{M?rc5F0s2Pg;m1{h0ctBr5t$`<4ihf(GnI7KLY5A! zj`p2D+ua;EmKx$(NirlmLmY!|WlTpb=00B9K4-T2VZlHl!)&gGbd{oosYEPi>fus< zad%Fsp3CD%#Ykl_0T0ckih*YTj)IP_RcsPt&WBwlGLwEP_(;a-Y37T~2aoBEtn6QO z)qSM5J5P7=!WeEgqtZ%?^a$x_{#WO@1@{5cTnbT%QxXv?&&LM_`b_nt^-kosxLGVv zFOH1rSANGlWa%yQAUUaClgdw^YejlHbO*uih^piJd-^0b&ipi1r6pS1`L*g{N*5VQ zeA&})@UEwj4#99cnV+7b9vAIx=@SIWqXZ114j=b~*Wc*7YUZNb~Jq<6yazIReB zgiJm7=xK>ouBTmYTO~JPa1+TgW^RC(;<2InB_laGf)jAPgMgUu5CI8X5yFoIA>FU* z3xvlBwyl3pL_qMslz+MeP zH-a-)r7m59-&YN63=J)9O|0zlJ815~7dvlBYuFMHP_rUGgqM_#j>Gs(rpoGe>TEAm$_jmllS0*O;9w%q?w2oWzf;-ys6m$k#kanAdNyGZR0e zE~m&WWo2W?EXaM5`{WS`GG=CGF`FAkB1-2j;2(!i;zvyE>~4wh@Hjd;ay#;ITiF=% z@CpkH^PD`zbLtcq+`(n*Y-xARiObTKWn+-dan2dq8rYcLvNN@^WJbolcHPR}PW;Fb z56`2T`ykq;j_x`kyi0>C+%*wG7e|HhnXr5$#xiox4o8l__YVnUI9l(9q7B_3Nw z#E)a*f*wS*GdQ;GUv?W=E490S>?wxh*CARaS&7=Mo86TawyS=zK%!<)< zG}Cw&?Wr5SjJb0iuGWPlG(|6X4UV*DC`3x|y3LO_U3wnBSdF) z)yw+r{EIG#6n>W^YTsQaK2vu!c_h8Hy8=>wV#lzR3oq zsPnW6X+bAMOlrZxD#}<6qxR|0e^<^%CGk*5Xc!bv$Vjq8d8>PcL^tN#eY4Eq5 z)WV3Ov!*XMPIQ+Vdzh+b`vy&iHo~jM-1S8x`Eg_@hkd}G!c?bDWv}MzWYZjIHKkn0|XbcDGI5dCki*RkB$DY}ug{Wjum_+rN zTCFX6h`%j%U%xzN^b1?$6BRWb0kfV8zN#ludxYQAUMH)t$QpRtJPVow(G?RQHpPQ+ zNsRm1+=7zN&)K1T=ZjPV2gCK zX}BC}ZRsP>@Q})vPSn9XeT-ah{_Go+PLghJ%7~U_ChLVK2mKQ$4)Gr+uaS$C5Kqis zU3?*8e{*`QQ?kU5iAjb#^M-@3pPV{;bT=~8WT>vvN&UA%ZbyS>*4Vfek&VNYvf7R5^h^5 z%wzk&1LJO_Tihb_^_@HF4#erY$ZTPOt<(8Mn%!XO z%qZiH{+%!X9tES0$o>1^j(?Z?zgLI43%ctNah%4#GvD9Rc033qJ$W!XHB}jHW*TWbO>fB z7ZWpJ*}AUhs`^WM+E-hWX%FIQgkgkzpHZya{)=X4YG7%z<0V=zIPy zwB+mt!}BY_e*0&&{8ki-N`w^pGMeh2k8B~;WsdL8@}@Fo`Wvf5g@cY6-1T1g_Zt$J zzZ05sC^sMcy@ZYVp4tiXeS7l|KKQGahWTC?ycza;zL!XVNT%0zd;j`sVir<#n2u^|0yppXC7`TloPe^CVW-%b6m zr2bb@|1_BYRlomLzrWlHxcIM4{jW{^Bl1B1*HQZII{w%DrTnk=`~P=HbDn0tae82) za(Hc#8!P)jYM^3U)4hD42q@5aGV6Zr%Su_>TRE_-))>JJ0O7#Nn)0 zQGVWrs#@Y&a}R#G>YEoBW0Drvh;LYN8%KOhgt8@rnATWl`tjyHw^4rNZ!`vVodUembngL(bvs)6X@9K` z&3GU-B04dPgyZS>7zxBeeL75dBrv$3;m^2zpTgq-&NGe%)yFSS`o!_q!XH^uH(dqEh&2=fR zt|=`(6_*e(HhJj}i@LF))i+=va;lW=n+s8ep-g3>41T?fI8*_XyS@-N`&yRYZ`im2 zl>q#a@Nm<2MJ>T_BiT)Vq#jYCL}}Q%knlu?quGBgMD92&#ksUv=bLI$re-PmJ8aAKj;)b!i^awuV3vnV`@Y(bP+i(5wm&73Y^#u^A zI6h1lzr-m$6vlEjESu$jeHU?W+-+35TaN=DeuoQS6cX-;#7#nNPU@f2#PGpw;a6<- z;0I?ui>b`m+^%0z_|FSg8py6Yqj&pl=!)1d;LP^p0_XnM$I!htZs@)J) zP}?uQHOKeQd7^Y+y9dkP;kO4<1W&PYC%*cv&i;*r%#Z|KV8rh*u?QQGU6{@Ie}4w^ z0fzSC3isV4JMgBZ#NBL$|3LN(Ap0`4qa>c}u`tT{gqI%wf$RZrTlhVjLwI6L!bh&P z0|fsh^gp-iHmVtDQLcNf8y~O?BAIobOCy`>{3qo$H9F=JBI>Cpv+&}`9}e5~E%4lf zf4_Zm6BYG9P8MBm;#0=Qli(?5GqW38=Ct`1{A2$_zl1~1dgx^U_CLHy9p3aQNWOH# z)S(wR!DvJlwj+KQ?&x?}=kl?VC$#$&BvJ!y#|E5?1j_aDeQX8B!O#+~j;*coIJDb09u%@{)6Sj>h+ICJH=CA(n|WDDFMBwv z)iRrFkL2?BX_%y_?WOjrOsEwil{}5?q@2mioS8qLH81}B640Go%IMvZ>sqR+WuY>+ zbo1#J8g4Q)|6L$CpYj=|4f3InfMq}M>hhxWZ346i`IAEeC&CiA1hP9^h78YO4TE(A zb)mjKzcS{@WKimx&^7@T?#FvsE-0sGV{0qWS>8N(r^9@Be9Ri+HQH$-%>IW>L45<& zF5v#i4nHlEeTcqfzh?A#Ym&J-l?qDq5mICS%9HPA92)yFAaNj;XF$C(&)opv8SyXIJUEE#$Jl;u#ev{i z;`teP_bwIw?HgjP?*YWeQ5ep_=AicPbnB+;e8{vmU#j6}RQZyYobv-JJ2!h}wzd2F zU5ZLy9ru}fq%vvl4k!(uzDQ3ACDEnP>@oUA%iNg;Ugyc5oT(W@iP=V_aogY=%8VhJ99m=C&(M1E)9&2?UJbSve>EWHt1Z#3p{|?X=Ter!8$oT0aBvlU0hQZRW(qJwpm6$CpWf)%! z$z!;!gm6!TjZ(q{Ei|~+uOa<%l*N5@DGj$&3pLa5QC(J)o=DS+{q6!Gfo)L!=II0YsU~3V4Q_rd2 z7^jvb*oQO2jHb6*djI(RAj6ceeW+H32R9q7sO6GtZckttmyrHgZ*8$L(2UV@c|tnL zSWafp%B9|ntSp)}w?q&p7OdNy1$?a5l3>Y0G8m+7^JRD$Q7ZtMNHTA)u%o^EKg?S3 zEGUtE)b&sCs_Z@zq})@9f;}?vK-1_t9p(n@IBfsUPi#^;n?ctitv&^%8A`~l*UPFX zWY=GfU@a!!o`~!&_47mEJ*|vV$gjN9(2~-JtD^|C6ZqDi@^b|5VS@XHVS4B53PPk^~$SDaCaE*CQE zf|m~@EfhOrI3y)IBPB>6MmU6xSRyy)FHwsF%JnDS?(7>a&-|UuS)b8!aIO=V1q9FH z-8Tu4t{Lk(1X4qG_^FYv3}0EaVW<0?&s3G9XW~lbXIs(X4^rBo;F~iS@;*~Yipc{w zPp~mY?X$<=)yVSMG@95}lPI6AyroeyTe%i#zApFWZlAPG8*l&RmS8TxthN^&+t9Np%r+38J^I5?AxCMDNX#bt6&F3)ZxCV_ z^r)1{Q*6oHDqcZs;#_=k$)V;~p!>!5z$DFAu#qd~ZNQ4W*NYy01e@KWEdqGB(+fWX zup}FN=IxQ0iv`M)yuH^I6W3|e0C;O3RWUKh@nB&oj=wry$Xe7jqb?z4{~mw&P)gC|-H67e!zue~EK zDesUBXY#pp$rV}vJU*f2dz&H-u4j2x;4F!ar|QTOlDa@g>>FQdyE-|FPh(8KK`rLX zb$Tp~$lJei9P3C1!a5FWv#qZdodQu^V8ZJXkQ;<5{Fh;OuB5a8P6$Ov+~GEbn;+6! zJ5(jHWG)-Z&yILfD@${Jg?C9y3((v8MDB#}85=de9jX;TFkL5-e8PvkuoQ2X-ZuBI zg8y19G8Di6D|hzrl1}Y-H~4mvT|L2izWh?{U?tnfmkz9KC!#x_gjwCstf)$uUuBpO zZ;jst+x<>JFMN>H{izyq3k5sR%mt-Y~kO=XmR z)Om8NCRk7|aOGPb|2_Epz;4R^%NXwV4)dYi$L}0Nj24CxhVnWdAdkO84ji&Z;`7xc z>wK)p5B&35Mb>jCrO5%9VSX9kLqUv;Gld2ct!Eyd&Jpthf*6$;$rb z8eXO7?0XT6YOi~Dwfjwg{9}G>JBy##zPsCZKSoT|Y@2P>!BaQN=vSOT*t+hJb3f!Y zC^?s96)E9dheWyG3Y)t!qO*bKdvEW8vjQR@VX1S0A?JCF&tDpT1 zx^De{2fCJHft}%xuEyu3Fkg@$JO`Dv5!!@afla54d#wO(dpN!{tc8y{QeOj*cl5P4 z_u~Vq>aTR}gW)ZyekFx90H3zJVJv@bRUKTTu0F{%=lI;&mWk!5s*7YLNVw7L9B@qT zG2yoDH-<{-ZDkSTU6Z7j|MBNDnd?Id8NT55vmmPYbk{?c6k9|`AbKFh-g9Grun1~Q z>-;o69dDuvYQ`wxiN}WE9iygfGU@8-hinBG*Y~;1=&x4e8#`%;@RhApe`mg@Y=oGw zXwTwMQc{IP@L4+qtlv`$B3A<1_rU&RvSmgu>BL>^a^}0d{Sy^rT-wb>hIHm2);Ybl zJZ11rPRIwYb8Ca}Li7K9>*~>22reJMn+=JdFG@`Mf1D_x!y1q>*eSjQpnft0CM5{= zwshelgT3-gn{xK#nhSKr?sM(IUi}w*4h7=m=U^FXM%ycSa(aTazqE5tbBi*EC?Po$*uw@{oZWk=LS*qJ}nTsj^D9HlP- z(iTzm-4Y+?^LwpNOeLvN+g8pztzLBk^!TECsda~QUorRwk>PW*tyZO<$a$9Y8c(A) zls}Sm#HyUz;9qbik!nsuc|Q%XDBIIDitjoM^J5-!*@(B1<)R@IzE&!^%In%*4jwKY zNf!srr6f*vip30x0$Vn zOUYBCWSJ^P=n;qzEX6BcBSi5AUGB#>hExv&eQ~;nS784Pi6v&60Z&i zEt_tIczwW?iMWZ>32(jSBMs^XKEkuD6ME<*w;m2_2w96C?q%tM?9Y3804gflIiJ^$ z`g(&K)Np~@-ti?dy%+79#i3K^QR2Y}$l|Em*Q;RtU*h8h{4@Ys*dGur%9AVZft_;2bLLjri&2Qh@OsHX-~>q zyeSxzsN3jahQkW2G3^JP6LZW3bPoFH_qpb|O|S4g`T?WMaY0~q^M1S?uzP%Xr851 z+*%B-0hOR*p3&~0vE(PY>h#iMaiCeaUPyK=bn*7KJ!QIjO1Wz?dW%YL8YAg#D|nO3 zPsA0~Tx4Q!Y`0gN+_KhS2mJm zhE(HA+9mG~?Z-2(33_@LtGJLe=z;44(ZRlu>thSjy!mdA)l#MC*o`V#=w6~kx{KVp z_D7aQgxfixw{2LRONiBZLz#tdIsuOnr6c!mDkoNk z2BvQ|xqaDYzqXXW7AD>%NAXZp^QIm<(y(>W<>X6UXC*qjudUJ)FMvfyhksbN1fMlQ zl%p8*2raPNy1h+T!f@$rYO+b3gCrAln5n}Cag-{dEU`Gjux`D(eQa;k;+F>$JRgGv z7e_K-ZjL>CD{&K`Lf9=o{eMx z&||2fe=>KH)r+n*TaaW3RCnk36s|>};WXJGSR%s%K*NUeL!jW-Nr7km7mi?^oQ3M} z72WDXpnL@EE4SG%jVyP5xQJsH1ZP+YJ(c)TbV!4Pba1lvjlNFkBQBECS~K}{V3&i? z^71C62l0pwirAS5y0#%QJ>Nx>{~{rVvIX+>Q^!)O)esJN7ZY&^iTZa zbsh4eBhM3JGg<Nzdd6(!msOqqR%eAqHr~^VdBSwiu|pKOhL&!q~VWA~4S_^CT90tS#=& zhQd^UqyJS~XKTO06qLk;tUMC&aD7drF#5IusLm|7lL}flMmviy^I@VJy?bTI9!8&3 z*Q|uVO1dyGF^+%*=X8c2}oiaJ(!{$tS#Bh-$A74Jd)OU0-G7#_`3I6GJl*F zSt2xGxlABQ=}HBT{P|*@>!4C_n~ib-xenA(3hork2;+lMT7eE=cnG zK&rlIpA4t}m@hPu^7u+D;&V0iN#eL|;J76rzFAMGo8kEP!sa*~$R1RgH)Nzr;?B-N zs)tjwhfQ+f67>a{!pzh+uF=don$*y{Hq3{co?Yr(lc-&rkJQ~3_JE+a(Dd6|!@^Ad zt2*{OM!?l_(FXUjdB*g9_IVnMCCTYd;7Z6#oV_QxGN6D|OP(YW4^Ga0Gxy-ho4@Yh z?tpzSxzH460j~eMF;kT;K;6TACu|E4H24{16_oCHS1E_T!s>oi>bAgD=#ITZ%6!ah(K@>YMQ3e<;jDA{CpkIa>qDu0PEaY%W27e zD>!HnAWnSMDjD@1E^0*f&P(;(?=7a{^j29mwVXd{gn9ciky&#?67dlWl>-8 z*{1o9xx8QonKQ*C)auN1Q^I{O1AMr09WosD#>O2;UdLMaP=bS;C?Xv^H(>VJk5^R3 zcPqH)`8m)SN4@xWZR|eg2coizey=WZ1=X&%IvI?Fx+enLpmn;-HgZWjKBfFbGE`Vf z5DyXw$tGrr?2%ZP9t1UTXe!Vbf*U87+#$$i9%3&GUZsOnmWPI5Tq}6$dl7Yt4HS`+oA(MXz>(J8P@W_6zbAA_})kNAnoRm zg2<3lfu7w@^NWr75hG!cD?xhvp`iS=g;vbz7H3xf>%b-1FhQ=cp3&)V3RSbwYDI6= z-a`o_(g&nki2QA|ndI^hDDqw65FJUqstwF)0o?q8i@_7p1lRq~Q(uY;WX*LD#JMzP zJC1z>PvH*@5NFm(J{O>^ z0};xF+D0H{yMpK9Hzct<*?J?=Du2bn?fW*SvWBZ4MxpxUy*%?CX^__jPsO4V(nBts zP$bs4m9Q9?RG*}kySBPy@OigTymoL27K&HK*wIZxV_a%;mw$h!oKU;6uw-t81C;NZ z9VBKU4Rxy4+{P3FB;zAEb+z2(g(^>91&_&q=EUjCP2gsw?x-`|wH}hAD99_g6dOIB zavxdAkIYTs)ZxK?o4K{pwh@No_ZrfD|Ll7!KME@1svJ7&5M>xK8GLB;z$Cid&oWEQ zJeCKh&q&1`%p+1Y^?}M!tWu9B)D<+!xJIOtw8KwcJ8Rbyn1uLlg5({L6Q_k@m@N{= z2d0-kp$*8b6&RTxvGNRqosE3OJrc%ms z2Js3C5R8Ra!;E<4PYbr-=KJG{YTK>{pwRH4g`zH6QJbu^M}{mWI;4xyeY(b6jV#}X zNir|R1N6T!Hr<|05zublDfM8F4~?E1?=kt8FGk`(YerAkJy`SG`pmh!Yr^6PE!KGMfp$uwZMjQKZ)ON34@FT@WT14(Ci&29CI~l8`_Y zJSg3rq+4t5X&i2^N_|lFT3!olZm*zoOmeiw?BhITQo+X()GL6pLxm7ecJ^_Mt$9v- z`dXDv+mf$05}MnZc5}_sI3_OQUE&}yQVjgl-h#N&r{FT}2hb=Tor41Sd|v8pB(m}+ z*@qG-3-&-_$CC-ZZ(BNUM4%a0BjtmU*`c&6(mVp#qhk}v|uAp#4dmhO3wVVD3p zHB7RU0!p*c24CB+mm-b#?*q1C1k`k43l) zzC+4zDXu@0JT1Nzcna2LB4=Byt}K-znavJ%i*+~HwcgyN-CRVn88MF*Z7tf>XX%{v zS-ki*1Wk-+VQeaJ&RtU1si%`KulG#WGS(ED~&jLbQY7H09! zHz%o$A&yvz+0LW8HWG`!c!LV549^Sj6XD$y1eGaZ|Az;^K@fdQw9hAsN&>{g(??fA z!j&#w#$dFbgDz8JkZhTx+J4i?T{nWq1A(vJm7AW#Yc@og-F(dYTeFcVbLw#AfE5Ko zyjds2Z6cio_r%=|J)Z?+{)TE#$HRbCi`k+ZHnutonI;sWe+3%IG-qEFz$piZAPeao zsH$liu~;2gP$Je|w(eY4v!X{~+BETeGCILc;drR?CLInP0luJA_1RQP9S9KozVvTD z{6MUFR*L}Jf0&)~|hY2|4^_&r)*jWsYLdtTfqUd9K-gJIqtRUPo$rF4_fSwtm5=Er(d3+F_1^ z7v3Gl(+L%f%=shyAQ>eDt%1y0?(i=tP3CNLlzw=nvyC)BBVU4-!2fKB1F z2Z(T-=)3Kks|g2ZHtyQKe{!XuSHTiaNv-`$3k-N6%@>grem|&z_P+Cq*yO*B5z)?& z-w#te)3^z%%>s3$)_v?xxrrxovoBqquJ2qH23y1;i6C8Pfgz=(T;5mfIp2c}K3XaU{R84v${UzTosi=uj-PTei_ zjY;svI!=#5I?en6s@+p39%4JdjzZS%71`pJmkdIUP>tzg!sb%5 zppc`+@@q{HUIXS$HLUsb`_ESvTvmm7Ud1g~Ti8oZEvbxsZJerFemPh*I0KFz7Nug5 zm&gsye{um4c@Y#wU%0C#(O7pv{fH5&J!4dMWVMKCN4zo0rhcjrq|@PKCzOY(9BqU) zWez`&-QaGR9Za~txp%fe`nVqu;ccWY*cBZc4u>6xEfw#q7b6Jrq!y=dsJFJ8UH++5 z(vz4y`dCh|`KS8>RfacT6{wraSlvK#U^0Qbh1A5S@#*AE`&;3SK&<*RHxCo3b_X&a zg0qD-eBh@fgL~S=s85ttTnpcAD=?S?s9BoWtgMzDX#Q{7tAh=*Eml0z!I5kq{AK15 zZnOn%v`lpA-@Ty>H|hO?28MO6vE4j^1cNkB2spscdd><1Q31(ddg4Cl*2%=l3+Wf! z9=q#I?Pw=0sO_3Fx*jxep9WiWv zvYGoJ&arZFV9hL=$c(Ed9}7vne@Kpl^kEToN}=Ke0p1Pzi+800&ynd#vl* zC}{QgX3r`e?5M5J5oZ_=r_<*>#TD;exi!-ZDIuQ!$_{ z$f?_(ZtxBI?RqeKcZ&uuB}UE(gRsqk-swBK*%QHtWGvP`EVz&%(scxxqsh9OrCGCa z&RNb_x?Y@y0KB!5aPAxRvOjj#2b7FiPO|8x{Ime$#$EZvs8FWdFntDo;?x`osY-T2 zK(Cw#v`>!QdJ*^J*oM8Z;~mQF&;5QRxn!KF!nHJYlWjJ+)DG!V*{Gnc?{XagI1=w) z`RA5sI-$Wa5X^8q9K)oOS4pBnGY{VMzNL{2J-5}VNIw8UaGHJZ z^5*dk*7Fe7(t~IY2r%o^c|R#@@cld>g@eN93Mo8J=0S}re@wtu^atq0hX^uB#B~ih zw98{{sk0PPVl>&{ywl6AL7n3Xa_$!5r4}Q}6h5PhhfrcSTpNLE>O@GDn~|>>f;Bt{ zpv*U&ced?0%}lLv!(X^^pu!iym?8Ks1g4Cws?_d&nMrQfH-(+0?&i_Sia4pCr`$Z`9p}_aVy1T?avmT&tx%);3CyTlJQ<1|D z5l{%2LdP3F)-k-as6SC)ZXYpl5w8!;84fWHB=%tr;nfNL>|z!FMU`;eliB zW3JZVUl^e?Vj^7L*A^i~Bc~~xkO5Suz3Zyngds=Y{nEDbkwBw1A33gP>02Kpkb_8? z-^zz;=4Rs-k@Maf^*r4<)9RE0SN^5cJXBr|2^kux`@0^&DQcww3KFz;NhwCldnzq1 zVmR`vY_~%W#WGnfS_S(}<#_yW`XF41W`v^g&|Wx`8P8PrP&&9^;L1Oh1aM>`LZhbb zyP0w2_K^9))wqyRL&%J(=YBwMBMs$Zy?5;v*_A|Uy3U5fso&%gWPb@HK6OoV1r_Hu z8yr-88x!AXv;j0irMbNf)sB?V*}pr&Id4U;9x!TV8ec?bkutfRlz?=xTICXC31|_7 zRmupJ<@(Wpv$eiRPX@$zsv(aOb)ASEu_;_pM z&T>uZG21LrI-iGsJ(geN39*q;Y~a$cz2zkaYCzqd95Dk?sp zR*}6ga%I@K8{%3YkCjD3zQ)}Ebh6=0K(ZUjki;)-N;bf*n9?KYz?7MRZ!x8C(B{*G zn8we&`8%6^xmPb?>k>fnCLkYYd^nH6q3M-3*XdkM<|HIlP)R&6nO-3uyl%h#)DPlF zJZ3vPig(9T(_yu8D_eX?FPaxx`cdO|hkg*+2`SD&R=5~Y67-NCGRbCve6-M!wD#s6 zh$(SIhVOEVY4mcg2FpQqko;N#q=pb@!EP{gG~&5#^dcZE9G6t0Y_rGUP)8<2o6KIW z=fA48>aNiE6F~ZBzv=}n=0hPd88RMKH8&=u`=VJT^Vx{(ZSBlweW?yr&wKw!#3y!}Ob>vIUeW+bAqrgB<*u)^^Nz%Z8uk6VGA4Yj(n#k`C|VhLD2_82XahXfdn zf*3#{{dLzwpdF(L%$mWcdpoV68=?eq*H>x3APp5UksdCyH=C(eFJDXKN^d%Xq%ejL zLAR8hg=Bi!w?%6jcSFnA)kfoEg2&aF32;Nt}R!s6+wbp-Ss2Rf$VU!MqBN$8PXAQ zBzJq73sia$uVuX;@rtTI#{Inx0B-fpOSEdf*T{0T$0TMmepzfX@WT;#vX2K5S?4eJ z@UVF5+#(x`Ztkr$)4STPv{pN$b@e27LQD_vL6y-DnW4=Tk$l_;_dsA8g0FNNwT<*f zus@=Gi0!C}x`;p)y5SVeCjePfO_am_aEAh}52)pJr1IU}f)5kYTTbMG2`hozSWvSE zQdytvqsayGBtzBfM%X}F(tvKz=uYJ|2}c3x#$3^?-|gNTf)RT_-S%syKts=CfS=ACybBwmnwuf4#?jG(|%G=5BeS0}4O?4-sAj_^4E4pr`* zm%)a+Lyw8<1MEq4GLlM*OBP9xersk&K-_``nllT|7;cy>>nXO~q4;H8@1^!s|M*j6~2wHp#?Ex$@3YpS4O=Au|W5T%)yMPlr4m<9|vn zAyGKEX+t@Ie4FtE4qB1bt{(VHZLjBkWCM0%kkZo-`-ikI$HHz9rt;G@0r-=CJ(2r4 z%pzSw@NV-($;BG!Ycc$M>SV>y(Jse6fZ8=q;*#TQj{dGm))rl$8rsa|)d?6k(@TeQ z0Ha^F{u3AspizQi4S);rEFLSD|)rfh;XrgYcXC)!W=NJYI83 z)d{&Mok3%=8>$ZuKiEwd~3S4M@hUCmS`ofi>_|CPiJ|MeOE{j)u3!= zK2rN6%;cBR%}a$^Ct|I&M&K}{u-a>spzx#eX?#KPO|LO7l`f$$s{d!%mYdo*#^?XH%^=ueV$@g2F z+t~2+SeJ;oe(3P?Kvw^AdP*k`C5Rh(OCOeC^|E+$>Uwosz$q{CzB2*$4w7{&vYtJh zP5UBgV_AE)SiyKwnAgron0Ijocfxn5d(};+zTO@sY~5zK=MMF5Ix?yxJEbg>cVi^) zNd&a}IG)-w7hzD8rl$JeG{F<_aNK?uINn`^_v#7a(qaQFJ`1hxb3&9Gr5y64I#j^& z>}z%ffvVOtE#rq~xGb}lv($Cux<}L-JtOxH@Z}~{p|jo_~uOi61sArS53ok+6M zsw{2x`I5NU#BqzC7(w($5;ao&Yu}S(*~@6>HRuQXR0KE2;=}6RUik2`na+y5qd_b} z{!J*CmVlk?>RcvFCis7T#SmsdUxU}z%OygqM$HV>nkV)!UcC1#WU{3*N-0oRR?Roq#M&Citt7<} z77brT=B&lx0b=M)%yWDV$q@Ha7~~_S65F-=-+vT$6QO@IF?;SJB1Qh`1;Eibg$09R zAPplelm=M??-vp(Iy`>r`XA;as{0R|W>>_re6j4o@yqLT^Bom*oYYRfouXR8L?E@G ze}W$z@5o^bPh<%roH5hXVvMPImlbI@+RV`y9pZBf8}}hiBS+G)8k((g?J{lX&^jg& z=eJ6Y_F8n57N&k@fg+js3Whmze2KC3PSB_uBw(XjZ}TQ3@7;@TJhaTGom34up}yoj zUWVtt3t=pxmeia}Gxt19rO?wS0f8p>Sk~0qVxQpmPc)pW#0muuolt4XPpVLkljDVA z5dx}M_cfq5%RN)vbAQE!%Bpdt3~0QShgRPMdYxy^4y_KKWk#U~)B53kCrgb->~Y_T z(5UQ)m9f~9qKp$htjk2L<;5HFv-BHG{={QP6S&J5T>iVH(lU@$DT^PM+Ft4IlOV6J z3Q3IsmB1kze;Bl-qw!9}=`sgYJQ=$ba+OgUDsjW=po`EN5#ovvungt1Xm1$D6Ex7x z7gu9d%FKl1=_rdJtw2|*$VXo0prAc>{;CKg>rHHL^R5V*Tyn~0Mj;!q7j!&KmlFkx zAb(7NyjmG6&&X?;G+!KBrOc3o=eT*05fxwXBImfU?e%ov0On*sYoU@uSI=$!HlZU2 z+mR9XaS)j9b?EN`_Ga3Qr5Vt0t8BP4;u)fb0;b2xNq9CoMuZXAjBM`{c}s@L&IY&E zDF=`{z8+<+H;%#C4cFb6>@Ip0!oM~N^Ht~MLisKPn^FCmspe5oismk2`Wi9Q(kr-{ zFMlG{%?Y}VkDl7=fhzIKywEY*@%9TvFpv6)oMm+Ck^C~fRhD}>tSw(OLT(DJhSuVl z+E9K9M~Vg3llp51qz)(LSkZ{8)46yljh;KHd+0xh{uyvzb?(WBKi*kI9b^^7tqwG= zIV~uJEUioP=7pwdky%}7oiSa0@Z%_NkJ>|aR-679sZg2T%WYrU8?8gfWV^K`CsUmj zh>WWybVIh3oWpqyX?5!^f=oPkL!1sRjrq9J*V-g>$_^J>*A|ajrpl& zvsYmSL7ds_A~QEfN)H@wXZNOaHH|EF`k=WySm9ojFLshcGqo@F!NobB9 zo0_p?^xQ&oBSJfZJ*wGb-~yJ9^IF%{XzpvvPKEWv%7@#hk14$2yXiH93q)J)<@bk%H7?lmzp?~(4i_ie56>FAk}1Li5EvW zPSM}Q>xcK&GbQoT5xb8_JAeaNhc;QbP)rGk?%V)#dA+~6Hd3T39J_eaDq4PzChBlF zcbK5HfXZvl@0TN*<^Y=ztO{7Uvn+)YkLaxr9c|9edhZs)fAcFT`s+L#clm3<&KvI^ zQ-n44AGSMoUf(mMPOV-^Yt4;RP&|T0e?Q$^v$-#XC=h&qlOAengGO~XNO(MbLCa<4 zpi#~n12f_`nfa&czduC3RHh5LWPI|E2N(NHKC)5o#ZZfiQ56CHX=S;3V1w}ZA6LW- zIxVBhv7h-jT|NBcIZI61++p@(NUSI8r&NOYS91#y8CN?(=+s~xAh_KuV+vy^pCgq)Qj}oGOGU_rS0GZvgKnY)7@8kqF&$l*+*vO8+l%5 zGWoV(A){4-#7|VV#n*@ZF(jP%dlNOh*A5F5^7_A#pcwixYZ>;Hpi<8MK(#SU=3BI& zRCSu6fz+29(;xQ>O`F}MbMlzeQZBh?8dBO{tWUugz63+>(v4v+WMjlRQY@#9xV26y z4rf1X)K;U2XUqIP>Vv)ZGfhm*<`e-elQ;haZ)hpMv9sEW!jAFbmC&if~Zs4pa<$cL+=;c6tlxSUZHY#rzrZgU^ z)aRSE$RGo2?23y}^Acla@|fbkSHRe4r3IUyB6gy1JiEFk4-sY1|NL#kSX{p&$CxhS z@B+-rh-RqQ%Z%iyXWh-V8=)Umjig*N58`XD_#gFR=M3#;TGH+@Oqg-d6ld+9T zDr0cD(@0E!VKumuzyn_gS0+4f6^Su~hD>E&T%LKGOh>lihrOeGUdiaT>DVVnC%m7C z%`e^zwqa=|lvcmtH1^3*&pI_?;xNr}7nA$I;{avr1sR=>b32an*crvdo5wl|?;CvN zT7m*19;&KCQT5ONn8x+9V$s~n9Cg>WLs&Q$62eZvfcDIw!jiXAN*8b0Z^=kARfRe4 zsfNKhf=c#Om57mAdbd-}8IzbQv|IZZGUe3vS|ze(k@o1Uk_)HSgWFXZ4s19pTJg9= z`%Xi*~XXg)U`8cS4&Vj2#Gxx;Dz+zccmC@7n6XXy6a zoPVccH+m#uAwe}w^P+X{W$JKzg}TXzZWw639l_E*IVTuooZMU~0hlZN6b*?n0toE><+Rph1TvYT)vg177>J#)ZRcT{yVSXaxj~&E6-UYU(IKZ^+ zF~mMoP$lKGR6sT5qhk=iM#0nNOt(CgZ^@7_*)v#6vbL8pxQfa8b^oWvqcQR)@=fc> zz6S9x$eOHv5Dn#%barrJtDBVGgYHYy<$lGKYL(uS+3nG{CjCw|gJxJ4Mn8N@E}13m zpFCl%{%+24h7inIVqz6=7qd*eDz}rx_cuHF->{qYB?en?dPe5 z@Ra6WD4m0p*9d&!C{)jAOV(98$`s3vYzYPG2!UHzOpD>*w>QdJcO`PtMeXNs@w;lsxLBE+)nF-zihoa}BjjAdyvm7aw8SiV(pDY^ad zOMMIx;;M9%y7FNHwO*GzRtW5kjr-;oYc=7U_(+#v?3C5STBq9l2T|h#k1H;t>_u}k z9-`K4y+l5LErLfM9*c)CvO$;mr!??`0q4<+GLgeOzhlY?9%1N4G&k+(IPkSLIf?p= z7mQvsmz9^LiD!lRifF5ptrZs4l=n#zf0AcO)F{~rYL`XnDs6emm?}I z73MV3E0gVJK5l;~hc-1RfVH|^fwv1=AAXp!aIo6N@}tT(4Z)R(#n-kH3&&fH!rQ3X zeP!#8t9|YWZr+PQ5utCa`PdxC=qM`*CU>B) z@P~=-FgGAOW6x<9Dqb}Fb}ygb6MOZtdE-x*8Oj;&puTiBO`g(RE+)PT3NvB zZmOrf_wLS9z?qx?&RdXBFzb;diio;fJPj2gKEx(m$PqR$CER({U@W2{)nn3?pXNo; zOc0JyN!4sK5t(z8FgIFTAGts&1-Z14&<|ZGULvt9-wCs+5@Xgq?76LwMbD46@LECj zL%LTyZFQQyXxCJ|zt(-WKZP9W11UHUXjIc2!qNEcksuwo{(DwomhS#MigvRA^njB* z>$pY`+4%z`2Mi(}y?gcISw*#_Ct*Y?-v)!NgSdiLPq<$1Wn#Q^O2#DQ027M&wpD5G zxLNP$F(gyWVY(4*wVW+6vlO=&I{P|QRaHT=)HZ}Ko$gYT+(so!yy+~qCU50hdG3iz zD{>(#--S9>-Nr|_apiMqH1T%6YVqF~v*P**S9IpO2`cPgEosi)2wR94d~|_jz*{5g zOXZ%`1dAdW<*~A-1JALMuV0rl2Dhy^yvd-e#Ykw+GtABGjpxll!81&^ztn`PCLyEy}5s)&d1Vk!gy@x z;8&h~?m-s|Zu{-NCfVVGGkDFR*Q3O)Nr0v=Lt{!DO_F(cVV3J$qtu-f_vG?I+wBwS z-{C!5#yjr!E%Ns_7du)bEY64D&$vB%Qinb*%*erQ-$)hc!=0#df7Cvc{y1LZgALB0?S82b5sNh9RRfh@7yiw&kR>w3&}PJ7^td4sDjhY!*Xl= zi3f0wQ`{g+6{^eTTy7lY)|x7{a*dJiND@BZ=eV#k)UI^HO?N-N2vO?cOG*TPk@a-h zo>++TxTd!cIa1G3GhenJ@ypX{YPu#x*6jZ)l`8&92BgN7jlaaPV|nz^ao>_dfIAQ-aW{z0VbWT`dPSD-6>>f@O-pDCG%=kOeBPNpsy zf>A80h-}plj-9j-MnSBGL?xownpY`XA4fT{jMuh5M=F(mP}M|7am^GrT7;8rCGs@S> za?R(Hgu!CvuUYQ7t9x-+Au31v8}WN<4h{K;bsf~@mUge+!?<VOrUDzY7vSy;0-Q>I*dOcBkxYII1~Pn8MvKHVo9 z%VIDhZ3mImCy8VwAx3^GWh4BuQ`60_DJoxpww{I|4CY+7Ke&2h<&r%VtFtcHsbfHn zmbHn0-%t^+i#>^o&{W?Oj*>XX;?mB~(l@jwY|B}9eQO`5NaNWQu^wbMM7=dxIJ<6l z=9(I;+e(iZk3k0W+$XaHJ*K)!Fw)v6AA@czzlZ7RakBBvKb}qnG^IW$$Y-nm@vKKG z8uH0EVm&_B0=7?^`Zdj;#9+CQ+8O)mR*I5T z=Vjf#<1bDtwxwvfEZe*<9{SDSb@0=%U$r=o(;3#36Fk=COqU)igc)Cw&0+79nM|48 zD~ZwK*<*ks5x-wuYssb%#q-9hygF5Dx6s&n1a9%Q%N~`^NFn!Is61;L_UmoYN_GH- z#rU*dbZ9;wvN(9ot^;eQ_Ju2<`cqL94Inzk3gJR*^X6&-_@R8W)rWjm3-M&r1YWal z-6rit?P1*+SFEloZ_SuT--Fw|$Gh87dGrd@W(K|IeeD7$1W22UqNVv7LE2H^vWiSS z`s2!}3AjQk5abWylRW5W@aIF_V`x$H7B8%Z;=JwY)g#E&9|ghCy&ni#oRgmtUFzS{ zNYF-S`I>xLaaxlF-%=3r)vw(24A-K8icNOm;ox(@#f8!8f{96{O1ecHnW_xINP=~t+wAA zk9-DFc`%w|d<3dp+3h&f-{I zAo1wo5{iPH7~G)w!k5>zOo5*$%J}fnKJ=Hk9z4}^@8EIlR{dzt(_7D7nUDiQw$Ee3VrZIO^#j)X*M@UmUuu>xTX9}B(7agemIB%~AEL%(#3~|R` zaFcF(Z;a(LK5{IO?7=glO2^(lk0SA%=*^RxEKPaY!SuWJs8iwhpGG}6*uxy|qzAIQ zd2d@3^k#fjnb6I3kd1r#E-X6Ahp2F=Yoc-uXIsyhP?MYIcq{EB^_58K;kRMP#wBa! zwoJgrb?aWW&f5Sb5+fP5wIAa!On5N@ue2~Hn-|DU{~7OsXx|v!dmh+ZrO-y0D@Sn! zaf`St@aYMSl0HVgN|&e6&NI7hS7WsT{w2&nu*d%O$eE|X#F8_HH;{aSqu{5^7FukR z;YpL^9lsA!sS9yRV?`64d9NXxs`|b0abcqC5zE=J3zT*I7VU~xmItVL17_exF^lz? zXZ*PA$XTeDr6I)G^B0;>WdI-ENJ}b{Qz`cP^ij^u!lIWB?pR!-$6wp}uIZ<6sr(vV)%!M`|0sXX_2$>U6GMw|D=J2f_Tb@?Y0LU^8} z?FyglE?JDfR~yiByzNBaIlXhWCz1{GrAkHR%vzkzJh-Wr>Zjc`jFSnT%wqYevFOdG zxY!tfp(#gp{K$hSp5NTlPn{BKc^xLZ&!XS;nzia}ObI1#d@(TUc_j*4m1UOOk%nxQ zrm%H>@t=~iM1Od+ErSP2&x2OjdxHQ3u?-}|C8p^VtJ<$<27M$!+7`)5aZkwV2_Yyq z^Q^m{YL2{bozqB=4GuyI3y_+iDG9k<4mFJoYh{+J4SLj56 z!FLM(3urqUy3ikfKnk&(&*Q7)!4VTX)M&W;{JS__wIs!>@L)7kp`ecUrA1B8a^c|7 z^J0*5FijnI*sK5^s6lqE7tRnWova z+N_Kq%K!$eGi<86J-oXv$YQ* zWA6TYM}3$souaX>v$k4x4ep)k_7AU_Te7tYSseC|i7G!Jk z_Y&-36q7Q_?iKMSe7->ndA>p#fnz(**Fy6AGF`G}^f*1sgx-UW*5WUMyeLI1m*wim z4;QDZH7Yq2J=T}mi&9)INNr~3mX!$$#Q}^-43NJcqnZuCEIHt>6rmg~On6QnqkOUd zScA&?($ANHWqBpXO4F(-6u!-IQ19uYx%WAI@D7honSIRV-pqVq0d(tK4z)7d^NMQu z+N{fc!+KRk+0SY2s+7XzdmY>C`gk3#(QNC0NJoG}OHoY=J$6VqpqOCNj3^nM#)jGfQ$xhGBRk<< zUAXWM^R51%&yuVBX2xMt4!&uz&F^o|46w4g1s4F!Xg`u+aP^{LXX^_rjp%co$9QHs z*iU@pEJcxEU1||>*d8>+u*XRS{tW17T6`pSO1etjWi8fS+rk-qK0q#>OM^0t(EN@eB_McnWIUZdRZ@SE?X#tS2uAg3J%W_n%0xs z%Y{=aOTE|oHeS0-oi*sZXw+n^Tq+qMG-2BXb#lS&mkT%z&x$8uDNICad-{<#~^MUP3WP8x3doY{`Q8G-TA=2 z&7MLDE4;hbe7xWmKGd%V8G3#vH6-Ns#iJ(|Rm#7n34WGu6Ie>SaM85eq~qvY`aFrT zg{I<-^Z14V`DL8PhOcto`&UDnj0u*L0hF=$KDOMFIdSvldotrmmiRq!jc>?a%FxjV zb!m2kN4>y7g0A&0Ximp8#n0(LQz`+`-ZyTqx z>MhaO>^G}tMi106t`3JS*tJU{Iqq~K7>#x78{O?Rbz>8F2i;G^W>A(lo(jkz#jbSO zZ<2w2%v$386Z<3`Q>cy($^{SX9i0ok^T^=dM}|H2wFVCFpKz%dKY?;BX?7`0)+lff7xmD!;`CROk6O_=+ovSQCcpaa zxUG1!JVR3{9)EHE!+j$!k)Bq9u9nDJ$=tJJv0>@D5Vmlxe7%McwzO5xhkEZKDm&D_ z)*+aX7`L_4RLD|A=%!-|fczo5Fy1i;P2FGXFQ5G`@dQ`ttf^c;F+B^`p6oqVADAob(8@t;Jl3t~!%j-K<=twe4ew7iT{_%S$j zDlGA6S-8lxNXwox>IvFKvtM4!i|*a9;RK*fw0@lfZ5|k0+Be0wnC7m)Xu9aCR+QCV zYrAaN0WC|Niyo8bj&*Mg_S+frXtI3ru3G=z4uNx^Qgg$9LQ62zzlTjejQVI%daUpw z$YBKqUn+QGdg7H}*Xt`G>;-D+X03QmMYRO&L)2GNbqdN}KCph_}*y~g63iP&lfUOV& zJzQd6ev<~Ve5)Qaap!#pnF1|3vO^4hZ+7}N^Cyqy5Zx`b&U29j>GJ?ziS{E02FL3W ze&D8(vtg!8T4x!v$jbf$R@l>uSUPLcn~Yt*8#|4T6!q7>PemF#PHW{q<5eg2{XDKS z50#H{xui{$kACp%H%`Nz(A$+RSN>r84mfp@3#CNpR#JP_pDUE~+`b~cpF59G68E~B zpA>c;$m~#?OANpyTet1=Ep~b=7J+!79yT%k50*4>rBK<^?e2|QQ#M`Mtn;C4h#}$t zz(CI@4RWA!x+U%1+yJy0$+VsD_}%FB!yfCSTVUBGPy}PPfVRfLozQHq*fHl4@yF!0 zLp8^i!EvF3HHPXUIP=U}vIU$Z$X}!%+13KsJdn@fKt>%2wc1{xVPu75#-S7T{B>1= z_OFseDWoDZjvO-@(-{Z@JrDoYcW?OVN-o}u=QC|ZReD<&4jr}|&k9nIEyrxgOW9b9b6)9qas?uQ7k8RS@xrhZaRcOC5i z1&uK2F{6Va6B3*S)#x@FGA3EG!KLR0Gp&B^Lq*@~(v-&B34#y22CBnEyYF{O!`l zD@wM{`(0&`mhNz&mLNEWJlou^S46F=T>zW@{5*s=f_PeygM^d(T>Jgo;p9*z5i(O% za85mw*`N3436#r1>c@{CzW_;}c1pk4VItnRKGNu&+Uixt5P9D*QimAXuK}!v4bfMX z3#@t?dQw@C;&JIZtuDXi>_;>5nwqYMZwT@9xtYIrrpd4h+{v1(&)weDj&m8pyC4Cc=LX@;fErEt{Wu?9!V@`Q{4II>Fku zH~OvXX#-%ir;1P=PZpP;P;kJgZ#1p^%Wbux7k1rg2ppp++&Li$aEw(dy(Rc_QPS!4 z@uz3;Qd^nCvnLfmRy2e`IE<)D7fX@dFfAp4hFe>xQHqdckMxD(It*(=ZzH|sLFfd` z23t^UQ8FJA6~0u&#EIag{vcrY zF%ZS*jyz{O<>|>o0#VGcgTS{H#kR;ip4TA82**?LZTeQk=%o|D=oy$d$vu&WgdU7| zYP&l*gsgS&ITfjB;WIQSRACbgFK>Uz^VkZ=VoyWoJK3EG^XeA0?H+hz=CiQZQjgz+ z?X&{6Ljo{caL?Wfk!{%kl}!nJpVo6YSYqvSFU7n+qwjSDkW{WW7w8S zhp#BHLS>18l=rG;z6HmH3m25rH1m##`cz21AQV!;hdp}-?o(-SaA@m`wqXU*(@2|A ziRr-3lYoM$qA@4U)*F&SKd&6?v)|b~bpUs^z<34A2o2+8+oq5+olxe0FMH#u?Xwk}NQDsNOasmvP*0pC`)f`Z z4=iP|1aLYseYG{Et)Jt}Wg#9F=U10vzO$P#A>HQt`$}s2c-+8V_%`BYbp*sb&RVgW z6Zh?^!GCxzt{{n7XH0@E-nI%2#44JA)`7` zxD`1i!-#Q)Yc?GI&+~5pOKw)LmT+Oa3{XLy9+V{hhd5JfvwQUnI)?JaX>}=mmRm2; z@F^9-u;G`mw7JCF8~bNXDnPMj^tYOw^UH1A

h4IX74TFL{jdB$ZVluHgwQZ7NIV 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%0DOg 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

tJnqvcakk9Y^GeC3f?>~Rb&+g{_ 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

$~Zg8Vof2+LNwl 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 Date: Thu, 16 Mar 2023 18:27:34 +0545 Subject: [PATCH 29/56] implementing additional scripts for stablecoin and dex --- config/external-addresses.json | 3 +- scripts/units/DEX/set-fee-to-setter.js | 29 ++++++++++++++++++ scripts/units/DEX/set-fee-to.js | 30 +++++++++++++++++++ .../delay-price-feed-pause.js | 25 ++++++++++++++++ .../delay-price-feed-unpause.js | 25 ++++++++++++++++ .../set-access-control-config.js | 30 +++++++++++++++++++ .../set-oracle.js | 30 +++++++++++++++++++ .../set-price-life.js | 30 +++++++++++++++++++ .../set-price.js | 25 ++++++++++++++++ .../set-time-delay.js | 30 +++++++++++++++++++ .../set-token-one.js | 30 +++++++++++++++++++ .../set-token-zero.js | 30 +++++++++++++++++++ 12 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 scripts/units/DEX/set-fee-to-setter.js create mode 100644 scripts/units/DEX/set-fee-to.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js create mode 100644 scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js diff --git a/config/external-addresses.json b/config/external-addresses.json index c3e2975..5e3ff94 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -15,5 +15,6 @@ "PRICE_ORACLE_ADDRESS":"", "BOOK_KEEPER_ADDRESS":"", "POSITION_MANAGER_ADDRESS":"", - "COLLATERAL_POOL_CONFIG_ADDRESS":"" + "COLLATERAL_POOL_CONFIG_ADDRESS":"", + "DEX_FACTORY_ADDRESS":"" } \ No newline at end of file diff --git a/scripts/units/DEX/set-fee-to-setter.js b/scripts/units/DEX/set-fee-to-setter.js new file mode 100644 index 0000000..b70ab55 --- /dev/null +++ b/scripts/units/DEX/set-fee-to-setter.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const txnHelper = require('../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const DEX_FACTORY_ADDRESS =addressesExternal.DEX_FACTORY_ADDRESS +const FEE_TO_SETTER = "" +const _encodeSetFeeToSetter = (_feeToSetter) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setFeeToSetter', + type: 'function', + inputs: [{ + type: 'address', + name: '_feeToSetter' + }] + }, [_feeToSetter]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetFeeToSetter(FEE_TO), + FEE_TO_SETTER, + "setFeeToSetterDEX" + ) +} \ No newline at end of file diff --git a/scripts/units/DEX/set-fee-to.js b/scripts/units/DEX/set-fee-to.js new file mode 100644 index 0000000..5dc8ea4 --- /dev/null +++ b/scripts/units/DEX/set-fee-to.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const DEX_FACTORY_ADDRESS =addressesExternal.DEX_FACTORY_ADDRESS +const TO_BE_WHITELISTED = "0x" +const FEE_TO = "" +const _encodeSetFeeTo = (_feeTo) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setFeeTo', + type: 'function', + inputs: [{ + type: 'address', + name: '_feeTo' + }] + }, [_feeTo]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetFeeTo(FEE_TO), + DEX_FACTORY_ADDRESS, + "setFeeToDEX" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js new file mode 100644 index 0000000..f3d6b80 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodePause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'pause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodePause(), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "pausePriceFeed" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js new file mode 100644 index 0000000..cddd37b --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeUnpause = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'unpause', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeUnpause(), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "unpausePriceFeed" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js new file mode 100644 index 0000000..7c70f00 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const ACCESS_CONTROL_CONFIG = "" +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetAccessControlConfig = (_accessControlConfig) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setAccessControlConfig', + type: 'function', + inputs: [{ + type: 'address', + name: '_accessControlConfig' + }] + }, [_accessControlConfig]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetAccessControlConfig(ACCESS_CONTROL_CONFIG), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setAccessControlConfig" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js new file mode 100644 index 0000000..bf73e69 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const ORACLE = "" +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetOracle = (_oracle) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setOracle', + type: 'function', + inputs: [{ + type: 'address', + name: '_oracle' + }] + }, [_oracle]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetOracle(ORACLE), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js new file mode 100644 index 0000000..e69e0bf --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SECOND = 1 +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetPriceLife = (_second) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPriceLife', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_second' + }] + }, [_second]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetPriceLife(SECOND), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setPriceLife" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js new file mode 100644 index 0000000..a232f84 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetPrice = () => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setPrice', + type: 'function', + inputs: [] + }, []); + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetPrice(), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setPriceDelayFathomOracle" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js new file mode 100644 index 0000000..005499e --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const SECOND = 1 +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetTimeDelay = (_second) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setTimeDelay', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_second' + }] + }, [_second]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetTimeDelay(SECOND), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setTimeDelay" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js new file mode 100644 index 0000000..bdbcd71 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const TOKEN_ONE = "" +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetToken1 = (_token) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setToken1', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [_token]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetToken1(TOKEN_ONE), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setToken1" + ) +} \ No newline at end of file diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js new file mode 100644 index 0000000..ae9a010 --- /dev/null +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const txnHelper = require('../../helpers/submitAndExecuteTransaction') +const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); +const addressesExternal = JSON.parse(rawdataExternal); + +const TOKEN_ZERO = "" +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS + +const _encodeSetToken0 = (_token) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'setToken0', + type: 'function', + inputs: [{ + type: 'address', + name: '_token' + }] + }, [_token]); + + return toRet; +} + + +module.exports = async function(deployer) { + + await txnHelper.submitAndExecute( + _encodeSetToken0(TOKEN_ZERO), + DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS, + "setToken0" + ) +} \ No newline at end of file From a5ae56bcdf08332e178ce04a62799821532e81eb Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 22 Mar 2023 12:30:46 +0545 Subject: [PATCH 30/56] working on scripts --- config/newly-generated-transaction-index.json | 8 ++ coralX-scenarios.js | 4 + docs/SCENARIOS-INSTRUCTIONS.md | 43 +++++--- package.json | 3 +- scripts/units/create_pool_dex.js | 8 +- scripts/units/create_pool_dex_xdc.js | 9 +- scripts/units/helpers/xdc3UtilsHelper.js | 19 ++++ .../units/uniswap-Exact-Tokens-For-Tokens.js | 104 ++++++++++++++++++ .../units/uniswap-Tokens-For-Exact-Tokens.js | 104 ++++++++++++++++++ 9 files changed, 284 insertions(+), 18 deletions(-) create mode 100644 scripts/units/helpers/xdc3UtilsHelper.js create mode 100644 scripts/units/uniswap-Exact-Tokens-For-Tokens.js create mode 100644 scripts/units/uniswap-Tokens-For-Exact-Tokens.js diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index d5053f7..5f18e27 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -25,6 +25,14 @@ { "id": 2, "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000000b" + }, + { + "id": 3, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004c" + }, + { + "id": 4, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004e" } ] } \ No newline at end of file diff --git a/coralX-scenarios.js b/coralX-scenarios.js index e6f3b18..7fc28ad 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -82,6 +82,10 @@ module.exports = { ['execute', '--path', 'scripts/units/execute-proposal.js', '--network', 'apothem'], ], + executeUniswapSwapTokens: [ + ['execute', '--path', 'scripts/units/uniswap-swap-two-tokens.js', '--network', 'apothem'], + ], + migrateAndConfigureForTests: [ ['compile'], diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index 6e287c1..5d58f11 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -1,13 +1,30 @@ -**How to Setup Council Stakes: //Note: This is only possible once as theres initializer for council stakes.** +# Scenarios Instruction + +## Table of contents + +- [Scenarios Instructions](#scenarios-instructions) + - [How to Setup Council Stakes](#How-to-Setup-Council-Stakes) + - [How to transfer tokens](#How-to-transfer-tokens) + - [How to Add Owners](#How-to-Add-owners) + - [How to create dex pool with Native Token](#How-to-create-dex-pool-with-Native-Token) + - [How to create dex pool with Two tokens](#How-to-create-dex-pool-with-Two-tokens) + - [How to create Proxy Wallet](#How-to-create-Proxy-Wallet) + - [How to Open Position](#How-to-Open-Position) + - [How to propose a proposal](#How-to-propose-a-proposal) + - [Queue Proposal](#Queue-Proposal) + - [Execute Proposal](#Execute-Proposal) + +## How to Setup Council Stakes +**//Note: This is only possible once as theres initializer for council stakes.** In file in scripts/units/setup_council_stakes.js 1. Hardcode COUNCIL_1, COUNCIL_2, COUNCIL_3 2. Hardcode T_TO_STAKE: Lock position for each council -3. Hardcode ​​T_TOTAL_TO_APPROVE: Total to approve. Should be T_TO_STAKE * Number of councils +3. Hardcode T_TOTAL_TO_APPROVE: Total to approve. Should be T_TO_STAKE * Number of councils 4. coralX scenario –run addCouncilStakesApothem -**How to transfer tokens from the scripts:** +## How to transfer tokens In this file scripts/units/transfer-tokens.js Hardcode: 1. T_TO_TRANSFER_PLACEHOLDER @@ -15,22 +32,22 @@ Hardcode: 3. coralX scenario --run transferTokenFromMultisigApothem -**How to Add Owners from the scripts** +## How to Add owners 1. In scripts/units/setup-multisig-owners.js Hardcode the COUNCIL_1 and COUNCIL_2 2. coralX scenario --run addOwnersToMultisigApothem -**How to create dex pool with Native Tokenr** +## How to create dex pool with Native Token 1. In scripts/units/create_pool_dex_xdc.js Hardcode the followings: -* TOKEN_ADDRESS: The token address to create a pair with XDC +* TOKEN_ADDRESS * AMOUNT_TOKEN_DESIRED * AMOUNT_TOKEN_MIN * AMOUNT_ETH_MIN * DEX_ROUTER_ADDRESS //this comes from external address -* TOKEN_ETH: The amount of ETH you are willing to spend +* TOKEN_ETH 2. coralX scenario --run createDexXDCPoolApothem -**How to create dex pool with Two tokens** +## How to create dex pool with Two tokens 1. In scripts/units/create_pool_dex.js Hardcode the followings: * Token_A_Address * Token_B_Address @@ -42,19 +59,19 @@ Hardcode: 2. coralX scenario --run createDexXDCPoolApothem -**How to create Proxy Wallet** +## How to create Proxy Wallet #### Note (VIMP): This can be called only once for any address. So, if I call once from multisig, it will create a proxy wallet whose address is stored in 'config/stablecoin-addresses-proxy-wallet.json'. This address is then used to create positions and this address never changes for a particular Multisig or EOA. 1. PROXY_WALLET_REGISTRY_ADDRESS will be taken from external addresses 2. coralX scenario --run createProxyWalletApothem -**How to Open Position** +## How to Open Position 1. In the file create_stablecoin_open_position.js hardcode: * XDC_COL //how much collateral to give * stablecoinAmount //stablecoin amount you want to receive * data //data you want to pass -**How to propose a proposal** +## How to propose a proposal 1. In the file propose-proposal.js Hardcode these: * What are the targets to propose to const TARGETS = [] @@ -66,7 +83,7 @@ Hardcode: 2. coralX scenario --run proposeProposalApothem -**Queue Proposal** +## Queue Proposal 1. In the file queue-proposal.js Hardcode these: * What are the targets to queue to const TARGETS = [] @@ -78,7 +95,7 @@ Hardcode: 2. coralX scenario --run queueProposalApothem -**Execute Proposal** +## Execute Proposal 1. In the file execute-proposal.js Hardcode these: * What are the targets to execute to diff --git a/package.json b/package.json index 56006f8..1f8daf2 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "solc": "0.8.13", "@openzeppelin/contracts": "^4.7.3", "@openzeppelin/contracts-upgradeable": "^4.7.3", - "@openzeppelin/upgrades-core": "^1.20.4" + "@openzeppelin/upgrades-core": "^1.20.4", + "xdc3": "^1.3.13416" }, "devDependencies": { "@openzeppelin/test-helpers": "^0.5.11", diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index f3f2acd..61f632b 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -2,6 +2,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') +const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); @@ -103,7 +104,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); //Will need to change it once it expires - const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(1000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), @@ -136,3 +137,8 @@ module.exports = async function(deployer) { } +async function getDeadlineTimestamp(deadline) { + return (await getCurrentTimestamp()) + deadline +} + + diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 9763222..ed5f4aa 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -6,8 +6,9 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); +const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") -const TOKEN_ADDRESS = "0xB01CF32918a010Dd35BDA8197F836c7D2447Ae58" //FTHM address +const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') @@ -80,7 +81,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const deadline = 1676577600/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_TOKEN_DESIRED), @@ -103,4 +104,6 @@ module.exports = async function(deployer) { ) } - +async function getDeadlineTimestamp(deadline) { + return (await getCurrentTimestamp()) + deadline +} diff --git a/scripts/units/helpers/xdc3UtilsHelper.js b/scripts/units/helpers/xdc3UtilsHelper.js new file mode 100644 index 0000000..de58c50 --- /dev/null +++ b/scripts/units/helpers/xdc3UtilsHelper.js @@ -0,0 +1,19 @@ +const XDCWeb3 = require('xdc3'); +const { networks } = require("../../../coralX-config") + + +async function getCurrentTimestamp () { + const web3 = getWeb3Instance() + var blockNumber = await web3.eth.getBlockNumber(); + var block = await web3.eth.getBlock(blockNumber); + var timestamp = block.timestamp; + return timestamp; + } + +module.exports = { + getCurrentTimestamp, +}; + +function getWeb3Instance(){ + return new XDCWeb3(new XDCWeb3.providers.HttpProvider(networks.apothem.host)) +} \ No newline at end of file diff --git a/scripts/units/uniswap-Exact-Tokens-For-Tokens.js b/scripts/units/uniswap-Exact-Tokens-For-Tokens.js new file mode 100644 index 0000000..97d6f56 --- /dev/null +++ b/scripts/units/uniswap-Exact-Tokens-For-Tokens.js @@ -0,0 +1,104 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') +const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") + +const txnHelper = require('./helpers/submitAndExecuteTransaction') + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); + +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); +const addressesExternal = JSON.parse(rawdataExternal); + +const Token_A_Address_FROM = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ +const Token_B_Address_TO = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC + +const AMOUNT_IN_TOKEN_A = web3.utils.toWei('2', 'ether') +const AMOUNT_OUT_MIN_TOKEN_B = web3.utils.toWei('0', 'ether') + +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeSwapExactTokensForTokens = ( + _amountIn, + _amountOutMin, + _path, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'swapExactTokensForTokens', + type: 'function', + inputs: [{ + type: 'uint256', + name: 'amountIn' + }, + { + type: 'uint256', + name: 'amountOutMin' + }, + { + type: 'address[]', + name: 'path' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'deadline' + }, + ] + }, [_amountIn, + _amountOutMin, + _path, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + + const path = [Token_A_Address_FROM, Token_B_Address_TO] + await txnHelper.submitAndExecute( + _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_IN_TOKEN_A), + Token_A_Address_FROM, + "ApproveTokenForSwap" + ) + + await txnHelper.submitAndExecute( + _encodeSwapExactTokensForTokens( + AMOUNT_IN_TOKEN_A, + AMOUNT_OUT_MIN_TOKEN_B, + path, + multiSigWallet.address, + deadline + ), + DEX_ROUTER_ADDRESS, + "SwapExactTokensForTokens", + ) +} + +async function getDeadlineTimestamp(deadline) { + return (await getCurrentTimestamp()) + deadline +} \ No newline at end of file diff --git a/scripts/units/uniswap-Tokens-For-Exact-Tokens.js b/scripts/units/uniswap-Tokens-For-Exact-Tokens.js new file mode 100644 index 0000000..83070f1 --- /dev/null +++ b/scripts/units/uniswap-Tokens-For-Exact-Tokens.js @@ -0,0 +1,104 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') +const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") + +const txnHelper = require('./helpers/submitAndExecuteTransaction') + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); + +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); +const addressesExternal = JSON.parse(rawdataExternal); + +const Token_A_Address_To = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ +const Token_B_Address_FROM = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC + +const AMOUNT_OUT_TOKEN_A = web3.utils.toWei('2', 'ether') +const AMOUNT_IN_TOKEN_B = web3.utils.toWei('0', 'ether') + +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeSwapTokensForExactTokens = ( + _amountIn, + _amountOutMin, + _path, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'swapTokensForExactTokens', + type: 'function', + inputs: [{ + type: 'uint256', + name: 'amountOut' + }, + { + type: 'uint256', + name: 'amountInMax' + }, + { + type: 'address[]', + name: 'path' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'deadline' + }, + ] + }, [_amountIn, + _amountOutMin, + _path, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + + const path = [Token_A_Address_To, Token_B_Address_FROM] + await txnHelper.submitAndExecute( + _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_IN_TOKEN_B), + Token_B_Address_FROM, + "ApproveTokenForSwap" + ) + + await txnHelper.submitAndExecute( + _encodeSwapTokensForExactTokens( + AMOUNT_OUT_TOKEN_A, + AMOUNT_IN_TOKEN_B, + path, + multiSigWallet.address, + deadline + ), + DEX_ROUTER_ADDRESS, + "SwapTokensForExactTokens", + ) +} + +async function getDeadlineTimestamp(deadline) { + return (await getCurrentTimestamp()) + deadline +} \ No newline at end of file From bb964fa9a783c8df6a370672b5a12dd3b005d50f Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 22 Mar 2023 14:56:09 +0545 Subject: [PATCH 31/56] scenarios doc --- docs/SCENARIOS-INSTRUCTIONS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index 5d58f11..2191d54 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -3,6 +3,7 @@ ## Table of contents - [Scenarios Instructions](#scenarios-instructions) + - [How if file Structure](#How-is-file-Structured) - [How to Setup Council Stakes](#How-to-Setup-Council-Stakes) - [How to transfer tokens](#How-to-transfer-tokens) - [How to Add Owners](#How-to-Add-owners) @@ -14,6 +15,17 @@ - [Queue Proposal](#Queue-Proposal) - [Execute Proposal](#Execute-Proposal) +## How is file Structured? + + +In config/external-addresses.json file all the addresses are stored that needs to be used externally + +In config/newly-generated-transaction-index.json file all the newly generated transaction indexes are stored. Note: It would be a good practise to make this file empty for each new deployment you do and each new deployment in another branch. + +config/external-addresses.json, config/newly-generated-transaction-index.json, stablecoin-addresses-proxy-wallet.json should not be deleted or the scenarios wont work + + + ## How to Setup Council Stakes **//Note: This is only possible once as theres initializer for council stakes.** From fed6ace3ce2009aa1e7d6191f1a43451a203606a Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 22 Mar 2023 19:19:28 +0545 Subject: [PATCH 32/56] added swaps for tokens in multisig scripts --- config/external-addresses.json | 3 +- config/newly-generated-transaction-index.json | 12 +++ contracts/dao/test/dex/IUniswapV2Router01.sol | 95 +++++++++++++++++++ contracts/dao/test/dex/IUniswapV2Router02.sol | 44 +++++++++ coralX-scenarios.js | 12 ++- scripts/units/create_pool_dex.js | 3 +- scripts/units/create_pool_dex_xdc.js | 4 +- scripts/units/swap-Exact-ETH-For-Tokens.js | 83 ++++++++++++++++ ...ens.js => swap-Exact-Tokens-For-Tokens.js} | 26 +++-- ...Tokens.js => swap-Tokens-For-Exact-ETH.js} | 48 ++++++---- 10 files changed, 292 insertions(+), 38 deletions(-) create mode 100644 contracts/dao/test/dex/IUniswapV2Router01.sol create mode 100644 contracts/dao/test/dex/IUniswapV2Router02.sol create mode 100644 scripts/units/swap-Exact-ETH-For-Tokens.js rename scripts/units/{uniswap-Exact-Tokens-For-Tokens.js => swap-Exact-Tokens-For-Tokens.js} (74%) rename scripts/units/{uniswap-Tokens-For-Exact-Tokens.js => swap-Tokens-For-Exact-ETH.js} (66%) diff --git a/config/external-addresses.json b/config/external-addresses.json index 5e3ff94..a173c32 100644 --- a/config/external-addresses.json +++ b/config/external-addresses.json @@ -16,5 +16,6 @@ "BOOK_KEEPER_ADDRESS":"", "POSITION_MANAGER_ADDRESS":"", "COLLATERAL_POOL_CONFIG_ADDRESS":"", - "DEX_FACTORY_ADDRESS":"" + "DEX_FACTORY_ADDRESS":"", + "WETH_ADDRESS":"0xE99500AB4A413164DA49Af83B9824749059b46ce" } \ No newline at end of file diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index 5f18e27..107cb89 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -34,5 +34,17 @@ "id": 4, "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004e" } + ], + "SwapExactETHForTokens": [ + { + "id": 1, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004f" + } + ], + "SwapTokensForExactETH": [ + { + "id": 1, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000051" + } ] } \ No newline at end of file diff --git a/contracts/dao/test/dex/IUniswapV2Router01.sol b/contracts/dao/test/dex/IUniswapV2Router01.sol new file mode 100644 index 0000000..4619967 --- /dev/null +++ b/contracts/dao/test/dex/IUniswapV2Router01.sol @@ -0,0 +1,95 @@ +pragma solidity >=0.6.2; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function removeLiquidityETH( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountToken, uint amountETH); + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountA, uint amountB); + function removeLiquidityETHWithPermit( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountToken, uint amountETH); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} diff --git a/contracts/dao/test/dex/IUniswapV2Router02.sol b/contracts/dao/test/dex/IUniswapV2Router02.sol new file mode 100644 index 0000000..1fc7b0a --- /dev/null +++ b/contracts/dao/test/dex/IUniswapV2Router02.sol @@ -0,0 +1,44 @@ +pragma solidity >=0.6.2; + +import './IUniswapV2Router01.sol'; + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountETH); + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 7fc28ad..2c156e4 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -82,8 +82,16 @@ module.exports = { ['execute', '--path', 'scripts/units/execute-proposal.js', '--network', 'apothem'], ], - executeUniswapSwapTokens: [ - ['execute', '--path', 'scripts/units/uniswap-swap-two-tokens.js', '--network', 'apothem'], + executeSwapTokensForExactETH: [ + ['execute', '--path', 'scripts/units/swap-Tokens-For-Exact-ETH.js', '--network', 'apothem'], + ], + + executeSwapExactTokensForTokens: [ + ['execute', '--path', 'scripts/units/swap-Exact-Tokens-For-Tokens.js', '--network', 'apothem'], + ], + + executeSwapExactETHForTokens: [ + ['execute', '--path', 'scripts/units/swap-Exact-ETH-For-Tokens.js', '--network', 'apothem'], ], diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 61f632b..0133d76 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -138,7 +138,6 @@ module.exports = async function(deployer) { async function getDeadlineTimestamp(deadline) { - return (await getCurrentTimestamp()) + deadline + return Math.floor(Date.now() / 1000) + deadline } - diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index ed5f4aa..1ada1bf 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -105,5 +105,5 @@ module.exports = async function(deployer) { } async function getDeadlineTimestamp(deadline) { - return (await getCurrentTimestamp()) + deadline -} + return Math.floor(Date.now() / 1000) + deadline +} \ No newline at end of file diff --git a/scripts/units/swap-Exact-ETH-For-Tokens.js b/scripts/units/swap-Exact-ETH-For-Tokens.js new file mode 100644 index 0000000..2ae443e --- /dev/null +++ b/scripts/units/swap-Exact-ETH-For-Tokens.js @@ -0,0 +1,83 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') +const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") + +const txnHelper = require('./helpers/submitAndExecuteTransaction') + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); +const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); + +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); +const addressesExternal = JSON.parse(rawdataExternal); +const WETH_ADDRESS = addressesExternal.WETH_ADDRESS +const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //WXDC + +const AMOUNT_IN_ETH = '2' +const SLIPPAGE = 0.05 + +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS + +const _encodeSwapExactETHForTokens = ( + _amountOutMin, + _path, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'swapExactETHForTokens', + type: 'function', + inputs: [ + { + type: 'uint256', + name: 'amountOutMin' + }, + { + type: 'address[]', + name: 'path' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'deadline' + }, + ] + }, [_amountOutMin, + _path, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const path = [WETH_ADDRESS, TOKEN_ADDRESS] + const amountIn = web3.utils.toWei(AMOUNT_IN_ETH, 'ether') + const amounts = await uniswapRouter.getAmountsOut(amountIn,path) + const amountOut = String(amounts[1] - (amounts[1] * SLIPPAGE)) + const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + + await txnHelper.submitAndExecute( + _encodeSwapExactETHForTokens( + amountOut, + path, + multiSigWallet.address, + deadline + ), + DEX_ROUTER_ADDRESS, + "SwapExactETHForTokens", + amountIn + ) +} + +async function getDeadlineTimestamp(deadline) { + return Math.floor(Date.now() / 1000) + deadline +} \ No newline at end of file diff --git a/scripts/units/uniswap-Exact-Tokens-For-Tokens.js b/scripts/units/swap-Exact-Tokens-For-Tokens.js similarity index 74% rename from scripts/units/uniswap-Exact-Tokens-For-Tokens.js rename to scripts/units/swap-Exact-Tokens-For-Tokens.js index 97d6f56..6378d6d 100644 --- a/scripts/units/uniswap-Exact-Tokens-For-Tokens.js +++ b/scripts/units/swap-Exact-Tokens-For-Tokens.js @@ -7,15 +7,16 @@ const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); +const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); -const Token_A_Address_FROM = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ -const Token_B_Address_TO = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC +const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ +const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC -const AMOUNT_IN_TOKEN_A = web3.utils.toWei('2', 'ether') -const AMOUNT_OUT_MIN_TOKEN_B = web3.utils.toWei('0', 'ether') +const AMOUNT_IN_TOKEN_A = '2' +const SLIPPAGE = 0.05 const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { @@ -76,29 +77,34 @@ const _encodeSwapExactTokensForTokens = ( module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const amountIn = web3.utils.toWei(AMOUNT_IN_TOKEN_A, 'ether') + const amounts = await uniswapRouter.getAmountsOut(amountIn,path) + const amountOut = String(amounts[1] - (amounts[1] * SLIPPAGE)) const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it - const path = [Token_A_Address_FROM, Token_B_Address_TO] + const path = [Token_A_Address, Token_B_Address] // swap from TokenA to receive TokenB await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_IN_TOKEN_A), - Token_A_Address_FROM, + Token_A_Address, "ApproveTokenForSwap" ) await txnHelper.submitAndExecute( _encodeSwapExactTokensForTokens( - AMOUNT_IN_TOKEN_A, - AMOUNT_OUT_MIN_TOKEN_B, + amountIn, + amountOut, path, multiSigWallet.address, deadline ), DEX_ROUTER_ADDRESS, - "SwapExactTokensForTokens", + "swapExactTokensForTokens", ) } async function getDeadlineTimestamp(deadline) { - return (await getCurrentTimestamp()) + deadline + return Math.floor(Date.now() / 1000) + deadline } \ No newline at end of file diff --git a/scripts/units/uniswap-Tokens-For-Exact-Tokens.js b/scripts/units/swap-Tokens-For-Exact-ETH.js similarity index 66% rename from scripts/units/uniswap-Tokens-For-Exact-Tokens.js rename to scripts/units/swap-Tokens-For-Exact-ETH.js index 83070f1..dab76c1 100644 --- a/scripts/units/uniswap-Tokens-For-Exact-Tokens.js +++ b/scripts/units/swap-Tokens-For-Exact-ETH.js @@ -7,15 +7,15 @@ const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); +const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); +const WETH_ADDRESS = addressesExternal.WETH_ADDRESS +const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" -const Token_A_Address_To = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ -const Token_B_Address_FROM = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC - -const AMOUNT_OUT_TOKEN_A = web3.utils.toWei('2', 'ether') -const AMOUNT_IN_TOKEN_B = web3.utils.toWei('0', 'ether') +const AMOUNT_OUT_ETH = '2' +const SLIPPAGE = 0.05 const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { @@ -34,17 +34,18 @@ const _encodeApproveFunction = (_account, _amount) => { return toRet; } -const _encodeSwapTokensForExactTokens = ( - _amountIn, - _amountOutMin, +const _encodeSwapTokensForExactETH = ( + _amountOut, + _amountInMax, _path, _to, _deadline ) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name: 'swapTokensForExactTokens', + name: 'swapTokensForExactETH', type: 'function', - inputs: [{ + inputs: [ + { type: 'uint256', name: 'amountOut' }, @@ -65,8 +66,8 @@ const _encodeSwapTokensForExactTokens = ( name: 'deadline' }, ] - }, [_amountIn, - _amountOutMin, + }, [_amountOut, + _amountInMax, _path, _to, _deadline]); @@ -77,28 +78,33 @@ const _encodeSwapTokensForExactTokens = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const path = [TOKEN_ADDRESS,WETH_ADDRESS] const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it - const path = [Token_A_Address_To, Token_B_Address_FROM] + const amountOut = web3.utils.toWei(AMOUNT_OUT_ETH,'ether') + const amounts = await uniswapRouter.getAmountsIn(amountOut, path) + const amountInMax = String(amounts[0] + (amounts[0] * SLIPPAGE)) + await txnHelper.submitAndExecute( - _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_IN_TOKEN_B), - Token_B_Address_FROM, - "ApproveTokenForSwap" + _encodeApproveFunction(DEX_ROUTER_ADDRESS,amountInMax), + TOKEN_ADDRESS, + "ApproveDexXDC" ) await txnHelper.submitAndExecute( - _encodeSwapTokensForExactTokens( - AMOUNT_OUT_TOKEN_A, - AMOUNT_IN_TOKEN_B, + _encodeSwapTokensForExactETH( + amountOut, + amountInMax, path, multiSigWallet.address, deadline ), DEX_ROUTER_ADDRESS, - "SwapTokensForExactTokens", + "SwapTokensForExactETH", ) } async function getDeadlineTimestamp(deadline) { - return (await getCurrentTimestamp()) + deadline + return Math.floor(Date.now() / 1000) + deadline } \ No newline at end of file From 40ca27b7cd5e5d200924018698d3814b15219265 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 23 Mar 2023 17:40:02 +0545 Subject: [PATCH 33/56] figuring out slippage for uniswap add liqudiity and swap --- config/newly-generated-transaction-index.json | 8 + coralX-scenarios.js | 7 + docs/SCENARIOS-INSTRUCTIONS.md | 8 +- scripts/units/add-liquidity-to-pool.js | 146 ++++++++++++++++++ scripts/units/add-liquidity-to-xdc-pool.js | 119 ++++++++++++++ scripts/units/create_pool_dex.js | 2 +- scripts/units/create_pool_dex_xdc.js | 7 +- scripts/units/swap-Exact-ETH-For-Tokens.js | 9 +- scripts/units/swap-Exact-Tokens-For-Tokens.js | 12 +- scripts/units/swap-Tokens-For-Exact-ETH.js | 9 +- 10 files changed, 311 insertions(+), 16 deletions(-) create mode 100644 scripts/units/add-liquidity-to-pool.js create mode 100644 scripts/units/add-liquidity-to-xdc-pool.js diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index 107cb89..ba1ad13 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -33,6 +33,14 @@ { "id": 4, "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004e" + }, + { + "id": 5, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000053" + }, + { + "id": 6, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000055" } ], "SwapExactETHForTokens": [ diff --git a/coralX-scenarios.js b/coralX-scenarios.js index 2c156e4..1484e74 100644 --- a/coralX-scenarios.js +++ b/coralX-scenarios.js @@ -94,6 +94,13 @@ module.exports = { ['execute', '--path', 'scripts/units/swap-Exact-ETH-For-Tokens.js', '--network', 'apothem'], ], + addLiquidityToPool: [ + ['execute', '--path', 'scripts/units/add-liquidity-to-pool.js', '--network', 'apothem'], + ], + + addLiquidityToXDCPool: [ + ['execute', '--path', 'scripts/units/add-liquidity-to-xdc-pool.js', '--network', 'apothem'], + ], migrateAndConfigureForTests: [ ['compile'], diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index 2191d54..185073a 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -3,7 +3,8 @@ ## Table of contents - [Scenarios Instructions](#scenarios-instructions) - - [How if file Structure](#How-is-file-Structured) + - [How is file Structure](#How-is-file-Structured) + - [How to setup at first](#How-to-setup-at-first) - [How to Setup Council Stakes](#How-to-Setup-Council-Stakes) - [How to transfer tokens](#How-to-transfer-tokens) - [How to Add Owners](#How-to-Add-owners) @@ -24,7 +25,10 @@ In config/newly-generated-transaction-index.json file all the newly generated tr config/external-addresses.json, config/newly-generated-transaction-index.json, stablecoin-addresses-proxy-wallet.json should not be deleted or the scenarios wont work - +## How to setup at first: +1. First you need to have addresses.json file in your root folder. This is automatically setup while doing deployment. +2. newly-generated-transaction-index.json must be made empty first for all the new deployment and must be taken into account that this does not in its own have context of the deployment. So for each new deployment-possibly on different branches, newly-generated-transaction-index.json must be cleared and then again it will be saved. +3. external-addresses.json must have the correct addresses as per newer deployments. ## How to Setup Council Stakes **//Note: This is only possible once as theres initializer for council stakes.** diff --git a/scripts/units/add-liquidity-to-pool.js b/scripts/units/add-liquidity-to-pool.js new file mode 100644 index 0000000..d300433 --- /dev/null +++ b/scripts/units/add-liquidity-to-pool.js @@ -0,0 +1,146 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') + +const txnHelper = require('./helpers/submitAndExecuteTransaction') + +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); + +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); +const addressesExternal = JSON.parse(rawdataExternal); + + +const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ +const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC +// SET AS Necessary +const Amount_A_Desired = web3.utils.toWei('2', 'ether') +const Amount_B_Desired = web3.utils.toWei('38', 'ether') +const Amount_A_Minimum = web3.utils.toWei('0', 'ether') +const Amount_B_Minimum = web3.utils.toWei('0', 'ether') + +// const Amount_A_Desired = web3.utils.toWei('250000', 'ether') +// const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') +// const Amount_A_Minimum = web3.utils.toWei('200000', 'ether') +// const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') +//What should +//const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeAddLiqudityFunction = ( + _tokenA, + _tokenB, + _amountADesired, + _amountBDesired, + _amountAMin, + _amountBMin, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'addLiquidity', + type: 'function', + inputs: [{ + type: 'address', + name: 'tokenA' + }, + { + type: 'address', + name: 'tokenB' + }, + { + type: 'uint256', + name: 'amountADesired' + }, + { + type: 'uint256', + name: 'amountBDesired' + }, + { + type: 'uint256', + name: 'amountAMin' + }, + { + type: 'uint256', + name: 'amountBMin' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'deadline' + }] + }, [_tokenA, + _tokenB, + _amountADesired, + _amountBDesired, + _amountAMin, + _amountBMin, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + //Will need to change it once it expires + const deadline = await getDeadlineTimestamp(1000) + //get the quote of token amount B that you want to add liquidity for + const tokenAmountB = await uniswapRouter.quote(Amount_A_Desired, Token_A_Address, Token_B_Address) + //account for slippage + const tokenAmountBOptimal = String(tokenAmountB- tokenAmountB*SLIPPAGE) + + await txnHelper.submitAndExecute( + _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), + Token_A_Address, + "ApproveTokenA", + 0 + ) + + await txnHelper.submitAndExecute( + _encodeApproveFunction(DEX_ROUTER_ADDRESS,tokenAmountBOptimal), + Token_B_Address, + "ApproveTokenB", + 0 + ) + await txnHelper.submitAndExecute( + _encodeAddLiqudityFunction( + Token_A_Address, + Token_B_Address, + Amount_A_Desired, + tokenAmountBOptimal, + Amount_A_Minimum, + Amount_B_Minimum, + multiSigWallet.address, + deadline + ), + DEX_ROUTER_ADDRESS, + "ApproveToken", + 0 + ) +} + + +async function getDeadlineTimestamp(deadline) { + return Math.floor(Date.now() / 1000) + deadline +} + diff --git a/scripts/units/add-liquidity-to-xdc-pool.js b/scripts/units/add-liquidity-to-xdc-pool.js new file mode 100644 index 0000000..515d710 --- /dev/null +++ b/scripts/units/add-liquidity-to-xdc-pool.js @@ -0,0 +1,119 @@ +const fs = require('fs'); +const constants = require('./helpers/constants') +const txnHelper = require('./helpers/submitAndExecuteTransaction') +const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); + +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); +const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); +const addressesExternal = JSON.parse(rawdataExternal); +const WETH_ADDRESS = addressesExternal.WETH_ADDRESS +const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address +const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') +const AMOUNT_ETH = web3.utils.toWei('3', 'ether') +const AMOUNT_ETH_MIN = web3.utils.toWei('0', 'ether') +const SLIPPAGE = 0.05 +//const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router +const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS + +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + + +const _encodeAddLiqudityFunction = ( + _token, + _amountTokenDesired, + _amountTokenMin, + _amountETHMin, + _to, + _deadline +) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'addLiquidityETH', + type: 'function', + inputs: [{ + type: 'address', + name: 'token' + }, + { + type: 'uint256', + name: 'amountTokenDesired' + }, + { + type: 'uint256', + name: 'amountTokenMin' + }, + { + type: 'uint256', + name: 'amountETHMin' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'deadline' + }] + }, [_token, + _amountTokenDesired, + _amountTokenMin, + _amountETHMin, + _to, + _deadline]); + + return toRet; +} + + +module.exports = async function(deployer) { + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); + const deadline = await getDeadlineTimestamp(10000) + const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + + //ReserveA/ReserveB = (ReserveA + QTYA)/(ReserveB + QTYB) + //ReserveA*ReserveB + ReserveA*QTYB = ReserveA*ReserveB +ReserveB*QRYA + //ie, ReserveA*QTYB = ReserveB*QTYA + + const tokenAmountA = await uniswapRouter.quote(AMOUNT_ETH, WETH_ADDRESS, TOKEN_ADDRESS) + const tokenAmountAOptimal = String(tokenAmountA- tokenAmountA*SLIPPAGE) + + + await txnHelper.submitAndExecute( + _encodeApproveFunction(DEX_ROUTER_ADDRESS,tokenAmountAOptimal), + TOKEN_ADDRESS, + "ApproveDexXDC" + ) + + await txnHelper.submitAndExecute( + _encodeAddLiqudityFunction( + TOKEN_ADDRESS, + tokenAmountAOptimal, + AMOUNT_TOKEN_MIN, + AMOUNT_ETH_MIN, + multiSigWallet.address, + deadline + ), + DEX_ROUTER_ADDRESS, + "createPoolWithXDC", + AMOUNT_ETH + ) +} + +async function getDeadlineTimestamp(deadline) { + return Math.floor(Date.now() / 1000) + deadline +} \ No newline at end of file diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 0133d76..2113ff2 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -104,7 +104,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); //Will need to change it once it expires - const deadline = await getDeadlineTimestamp(1000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(1000) await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 1ada1bf..6be7a86 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -6,16 +6,15 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); const addressesExternal = JSON.parse(rawdataExternal); -const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') +const AMOUNT_TOKEN_ETH = web3.utils.toWei('3', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS -const TOKEN_ETH = web3.utils.toWei('3', 'ether') const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -81,7 +80,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(10000) await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_TOKEN_DESIRED), @@ -100,7 +99,7 @@ module.exports = async function(deployer) { ), DEX_ROUTER_ADDRESS, "createPoolWithXDC", - TOKEN_ETH + AMOUNT_TOKEN_ETH ) } diff --git a/scripts/units/swap-Exact-ETH-For-Tokens.js b/scripts/units/swap-Exact-ETH-For-Tokens.js index 2ae443e..eea63e7 100644 --- a/scripts/units/swap-Exact-ETH-For-Tokens.js +++ b/scripts/units/swap-Exact-ETH-For-Tokens.js @@ -1,6 +1,5 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") const txnHelper = require('./helpers/submitAndExecuteTransaction') @@ -56,14 +55,18 @@ const _encodeSwapExactETHForTokens = ( module.exports = async function(deployer) { - + //we want to swap exact eth for tokens const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + //we set path to swap from WETH and receive Token const path = [WETH_ADDRESS, TOKEN_ADDRESS] + // we set exact eth that we want to swap const amountIn = web3.utils.toWei(AMOUNT_IN_ETH, 'ether') + // we get how much tokens we can receive for the exact eth const amounts = await uniswapRouter.getAmountsOut(amountIn,path) + // we set slippage to determine lowest amount of Tokens we are willing to receive taking Slippage into account const amountOut = String(amounts[1] - (amounts[1] * SLIPPAGE)) - const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(10000) await txnHelper.submitAndExecute( _encodeSwapExactETHForTokens( diff --git a/scripts/units/swap-Exact-Tokens-For-Tokens.js b/scripts/units/swap-Exact-Tokens-For-Tokens.js index 6378d6d..42fc9bf 100644 --- a/scripts/units/swap-Exact-Tokens-For-Tokens.js +++ b/scripts/units/swap-Exact-Tokens-For-Tokens.js @@ -1,6 +1,5 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") const txnHelper = require('./helpers/submitAndExecuteTransaction') @@ -77,15 +76,22 @@ const _encodeSwapExactTokensForTokens = ( module.exports = async function(deployer) { + //swap exact tokens for tokens + const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + //amounts In is the fixed amount you want to give to the uniswap pool const amountIn = web3.utils.toWei(AMOUNT_IN_TOKEN_A, 'ether') + //now, we set path where Token A is swapped to Token B + const path = [Token_A_Address, Token_B_Address] // swap from TokenA to receive TokenB + // we get amountsOut from the router which gives us the value of how much amount we can receive const amounts = await uniswapRouter.getAmountsOut(amountIn,path) + // we caculate minimum amount of tokens we want with slippage as percentage subtracted from amount const amountOut = String(amounts[1] - (amounts[1] * SLIPPAGE)) - const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it + const deadline = await getDeadlineTimestamp(10000) - const path = [Token_A_Address, Token_B_Address] // swap from TokenA to receive TokenB + await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,AMOUNT_IN_TOKEN_A), Token_A_Address, diff --git a/scripts/units/swap-Tokens-For-Exact-ETH.js b/scripts/units/swap-Tokens-For-Exact-ETH.js index dab76c1..b0c0436 100644 --- a/scripts/units/swap-Tokens-For-Exact-ETH.js +++ b/scripts/units/swap-Tokens-For-Exact-ETH.js @@ -1,6 +1,5 @@ const fs = require('fs'); const constants = require('./helpers/constants') -const {getCurrentTimestamp} = require("./helpers/xdc3UtilsHelper") const txnHelper = require('./helpers/submitAndExecuteTransaction') @@ -77,13 +76,17 @@ const _encodeSwapTokensForExactETH = ( module.exports = async function(deployer) { + // we want to swap tokens for exact eth const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + //path to swap Token to Fixed ETH const path = [TOKEN_ADDRESS,WETH_ADDRESS] - const deadline = await getDeadlineTimestamp(10000)/* ZERO_AM_UAE_TIME_SEVENTEEN_FEB_TIMESTAMP*/+ 100 * 86400 //NOTE: Please change it - + const deadline = await getDeadlineTimestamp(10000) + //we set amounts out, ie fixed amount of eth we want to get const amountOut = web3.utils.toWei(AMOUNT_OUT_ETH,'ether') + //now we are retreiving how much amount of Token we can get for that eth const amounts = await uniswapRouter.getAmountsIn(amountOut, path) + //now we are adding slippage to mark the maximum amount of token we want to swap for const amountInMax = String(amounts[0] + (amounts[0] * SLIPPAGE)) await txnHelper.submitAndExecute( From 961ba0578cbdd00d1994747e2108487a7e26f166 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 23 Mar 2023 18:39:22 +0545 Subject: [PATCH 34/56] accounting for slippage for adding liquidity --- config/newly-generated-transaction-index.json | 20 +++++++++++++++++++ scripts/units/add-liquidity-to-pool.js | 11 ++++++++-- scripts/units/add-liquidity-to-xdc-pool.js | 19 +++++++++--------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index ba1ad13..d39974a 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -41,6 +41,22 @@ { "id": 6, "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000055" + }, + { + "id": 7, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000057" + }, + { + "id": 8, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005b" + }, + { + "id": 9, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005f" + }, + { + "id": 10, + "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000061" } ], "SwapExactETHForTokens": [ @@ -53,6 +69,10 @@ { "id": 1, "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000051" + }, + { + "id": 2, + "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005d" } ] } \ No newline at end of file diff --git a/scripts/units/add-liquidity-to-pool.js b/scripts/units/add-liquidity-to-pool.js index d300433..0b4946e 100644 --- a/scripts/units/add-liquidity-to-pool.js +++ b/scripts/units/add-liquidity-to-pool.js @@ -108,6 +108,13 @@ module.exports = async function(deployer) { const tokenAmountB = await uniswapRouter.quote(Amount_A_Desired, Token_A_Address, Token_B_Address) //account for slippage const tokenAmountBOptimal = String(tokenAmountB- tokenAmountB*SLIPPAGE) + + + //Get Amounts out ie, if we give Amount_A_Desired of TokenA how much token we can receive back as Token B + const TOKEN_AMOUNT_B = await uniswapRouter.getAmountsOut(Amount_A_Desired,[Token_A_Address,Token_B_Address]) + //Account for slippage + const TOKEN_AMOUNT_MAX_B = String(TOKEN_AMOUNT_B[1] + (TOKEN_AMOUNT_B[1]*SLIPPAGE)) + const TOKEN_AMOUNT_MIN_B = String(TOKEN_AMOUNT_B[1] - (TOKEN_AMOUNT_B[1]*SLIPPAGE)) await txnHelper.submitAndExecute( _encodeApproveFunction(DEX_ROUTER_ADDRESS,Amount_A_Desired), @@ -127,9 +134,9 @@ module.exports = async function(deployer) { Token_A_Address, Token_B_Address, Amount_A_Desired, - tokenAmountBOptimal, + TOKEN_AMOUNT_MAX_B, Amount_A_Minimum, - Amount_B_Minimum, + TOKEN_AMOUNT_MIN_B, multiSigWallet.address, deadline ), diff --git a/scripts/units/add-liquidity-to-xdc-pool.js b/scripts/units/add-liquidity-to-xdc-pool.js index 515d710..2e9dfd2 100644 --- a/scripts/units/add-liquidity-to-xdc-pool.js +++ b/scripts/units/add-liquidity-to-xdc-pool.js @@ -11,8 +11,8 @@ const addressesExternal = JSON.parse(rawdataExternal); const WETH_ADDRESS = addressesExternal.WETH_ADDRESS const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') -const AMOUNT_ETH = web3.utils.toWei('3', 'ether') -const AMOUNT_ETH_MIN = web3.utils.toWei('0', 'ether') +const AMOUNT_ETH = web3.utils.toWei('100', 'ether') +const AMOUNT_ETH_MIN = web3.utils.toWei('50', 'ether') const SLIPPAGE = 0.05 //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS @@ -88,13 +88,14 @@ module.exports = async function(deployer) { //ReserveA/ReserveB = (ReserveA + QTYA)/(ReserveB + QTYB) //ReserveA*ReserveB + ReserveA*QTYB = ReserveA*ReserveB +ReserveB*QRYA //ie, ReserveA*QTYB = ReserveB*QTYA - - const tokenAmountA = await uniswapRouter.quote(AMOUNT_ETH, WETH_ADDRESS, TOKEN_ADDRESS) - const tokenAmountAOptimal = String(tokenAmountA- tokenAmountA*SLIPPAGE) - + //Get Amounts out ie, if we give AMOUNT_ETH of ETH how much token we can receive back as Token + const TOKEN_AMOUNT = await uniswapRouter.getAmountsOut(AMOUNT_ETH,[WETH_ADDRESS,TOKEN_ADDRESS]) + //Account for slippage + const TOKEN_AMOUNT_MAX = String(TOKEN_AMOUNT[1] + (TOKEN_AMOUNT[1]*SLIPPAGE)) + const TOKEN_AMOUNT_MIN = String(TOKEN_AMOUNT[1] - (TOKEN_AMOUNT[1]*SLIPPAGE)) await txnHelper.submitAndExecute( - _encodeApproveFunction(DEX_ROUTER_ADDRESS,tokenAmountAOptimal), + _encodeApproveFunction(DEX_ROUTER_ADDRESS,TOKEN_AMOUNT_MAX), TOKEN_ADDRESS, "ApproveDexXDC" ) @@ -102,8 +103,8 @@ module.exports = async function(deployer) { await txnHelper.submitAndExecute( _encodeAddLiqudityFunction( TOKEN_ADDRESS, - tokenAmountAOptimal, - AMOUNT_TOKEN_MIN, + TOKEN_AMOUNT_MAX, + TOKEN_AMOUNT_MIN, AMOUNT_ETH_MIN, multiSigWallet.address, deadline From 019239bf1d557a21c2da3958400ebcf21528aaab Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Mon, 27 Mar 2023 02:28:09 +0400 Subject: [PATCH 35/56] Post-audit refactoring in style and performance --- .solhint.json | 11 +- .solhintignore | 2 + contracts/common/Address.sol | 37 +- contracts/common/SafeERC20.sol | 42 +- contracts/common/SafeERC20Staking.sol | 13 +- contracts/common/cryptography/ECDSA.sol | 26 +- contracts/common/cryptography/EIP712.sol | 6 +- contracts/common/math/BoringMath.sol | 1 - contracts/common/math/FullMath.sol | 8 +- contracts/common/proxy/StakingProxy.sol | 8 +- contracts/common/proxy/StakingProxyAdmin.sol | 2 +- contracts/common/proxy/VaultProxy.sol | 6 +- .../common/proxy/transparent/ProxyAdmin.sol | 8 +- .../TransparentUpgradeableProxy.sol | 6 +- contracts/dao/governance/Governor.sol | 503 ++++++++++-------- .../dao/governance/MainTokenGovernor.sol | 159 +++--- .../dao/governance/TimelockController.sol | 153 ++++-- .../extensions/GovernorCountingSimple.sol | 22 +- .../extensions/GovernorSettings.sol | 23 +- .../extensions/GovernorTimelockControl.sol | 50 +- .../governance/extensions/GovernorVotes.sol | 10 +- .../GovernorVotesQuorumFraction.sol | 11 +- .../extensions/IGovernorTimelock.sol | 12 +- .../dao/governance/extensions/IVotes.sol | 9 +- .../governance/interfaces/IEmergencyStop.sol | 11 + .../dao/governance/interfaces/IGovernor.sol | 107 ++-- .../dao/governance/interfaces/IRelay.sol | 21 + .../interfaces/ISupportingTokens.sol | 19 + .../interfaces/ITimelockController.sol | 62 ++- contracts/dao/staking/StakingStorage.sol | 1 - .../staking/helpers/IStakingGetterHelper.sol | 16 +- .../dao/staking/helpers/IStakingHelper.sol | 2 +- .../staking/helpers/StakingGettersHelper.sol | 76 ++- .../staking/interfaces/IRewardsHandler.sol | 8 +- .../dao/staking/interfaces/IStakingGetter.sol | 21 +- .../staking/interfaces/IStakingHandler.sol | 18 +- .../staking/interfaces/IStakingStorage.sol | 2 +- .../dao/staking/library/RewardsLibrary.sol | 126 ----- .../dao/staking/library/StakingLibrary.sol | 44 -- .../staking/packages/RewardsCalculator.sol | 118 +++- .../dao/staking/packages/RewardsInternals.sol | 43 +- .../dao/staking/packages/StakingGetters.sol | 46 +- .../dao/staking/packages/StakingHandler.sol | 275 ++++++---- .../dao/staking/packages/StakingInternals.sol | 98 ++-- .../dao/staking/vault/interfaces/IVault.sol | 17 +- .../staking/vault/packages/VaultPackage.sol | 87 +-- contracts/dao/test/ERC20Rewards1.sol | 43 +- contracts/dao/test/ERC20Rewards2.sol | 43 +- contracts/dao/test/dex/IUniswapV2Router01.sol | 70 ++- contracts/dao/test/dex/IUniswapV2Router02.sol | 14 +- .../dao/test/stablecoin/IProxyRegistry.sol | 2 +- .../dao/test/token-factory/ERC20Factory.sol | 12 +- .../interfaces/IERC20Factory.sol | 6 +- .../dao/test/token-timelock/TokenTimelock.sol | 6 +- .../token-timelock/TokenTimelockFactory.sol | 10 +- contracts/dao/tokens/ERC20/ERC20.sol | 111 ++-- contracts/dao/tokens/ERC20/IERC20.sol | 6 +- .../tokens/ERC20/extensions/ERC20Permit.sol | 39 +- .../tokens/ERC20/extensions/ERC20Votes.sol | 72 +-- .../tokens/ERC20/extensions/IERC20Permit.sol | 10 +- contracts/dao/tokens/IVMainToken.sol | 4 +- contracts/dao/tokens/MainToken.sol | 7 +- contracts/dao/tokens/VMainToken.sol | 70 ++- contracts/dao/treasury/MultiSigWallet.sol | 235 ++++---- .../treasury/interfaces/IMultiSigWallet.sol | 20 +- .../migrations/test/3_add_vault_operator.js | 4 +- scripts/tests/dao/demo/staking.demo.test.js | 7 +- scripts/tests/dao/full-flow-demo.test.js | 7 +- .../dao/governance/multisig-treasury.test.js | 25 +- .../dao/governance/proposal-flow.test.js | 72 +-- .../token-creation-though-gov.test.js | 13 +- scripts/tests/dao/staking/staking.test.js | 31 +- ...-bookkeeper.js => allowlist-bookkeeper.js} | 14 +- ...-bookkeeper.js => blocklist-bookkeeper.js} | 14 +- truffle-config.js | 22 + 75 files changed, 1676 insertions(+), 1659 deletions(-) create mode 100644 contracts/dao/governance/interfaces/IEmergencyStop.sol create mode 100644 contracts/dao/governance/interfaces/IRelay.sol create mode 100644 contracts/dao/governance/interfaces/ISupportingTokens.sol delete mode 100644 contracts/dao/staking/library/RewardsLibrary.sol delete mode 100644 contracts/dao/staking/library/StakingLibrary.sol rename scripts/units/stablecoin/book-keeper/{blacklist-bookkeeper.js => allowlist-bookkeeper.js} (67%) rename scripts/units/stablecoin/book-keeper/{whitelist-bookkeeper.js => blocklist-bookkeeper.js} (67%) create mode 100644 truffle-config.js diff --git a/.solhint.json b/.solhint.json index e75e282..f33f8c0 100644 --- a/.solhint.json +++ b/.solhint.json @@ -2,8 +2,8 @@ "extends": "solhint:recommended", "plugins": [], "rules": { - "compiler-version": ["error",">=0.8.0 <0.9.0"], - "max-line-length": ["error",120], + "compiler-version": ["error","0.8.16"], + "max-line-length": ["error", 150], "reason-string": ["error", {"maxLength": 96}], "func-visibility": ["error", {"ignoreConstructors": true}], "not-rely-on-time": "error", @@ -11,14 +11,15 @@ "comprehensive-interface": "error", "func-param-name-mixedcase": "error", "modifier-name-mixedcase": "error", - "code-complexity": ["error",7], - "function-max-lines": ["error",50], + "code-complexity": ["error", 7], + "function-max-lines": ["error", 50], "const-name-snakecase": "error", "no-complex-fallback": "off", "no-inline-assembly": "off", "private-vars-leading-underscore": "off", "avoid-low-level-calls": "off", "no-empty-blocks": "off", - "max-states-count": "off" + "max-states-count": "off", + "no-global-import": "off" } } diff --git a/.solhintignore b/.solhintignore index e69de29..6a6547c 100644 --- a/.solhintignore +++ b/.solhintignore @@ -0,0 +1,2 @@ +/contracts/common +/contracts/dao/test \ No newline at end of file diff --git a/contracts/common/Address.sol b/contracts/common/Address.sol index dd23940..d09b2e0 100644 --- a/contracts/common/Address.sol +++ b/contracts/common/Address.sol @@ -62,11 +62,7 @@ library Address { * * _Available since v3.1._ */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } @@ -81,11 +77,7 @@ library Address { * * _Available since v3.1._ */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } @@ -95,12 +87,7 @@ library Address { * * _Available since v3.1._ */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); @@ -124,11 +111,7 @@ library Address { * * _Available since v3.4._ */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); @@ -185,11 +168,7 @@ library Address { * * _Available since v3.3._ */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); @@ -210,11 +189,7 @@ library Address { * * _Available since v4.3._ */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { + function verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) internal pure returns (bytes memory) { if (success) { return returndata; } else { diff --git a/contracts/common/SafeERC20.sol b/contracts/common/SafeERC20.sol index 952c224..23c329c 100644 --- a/contracts/common/SafeERC20.sol +++ b/contracts/common/SafeERC20.sol @@ -19,20 +19,11 @@ import "./Address.sol"; library SafeERC20 { using Address for address; - function safeTransfer( - IERC20 token, - address to, - uint256 value - ) internal { + function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) internal { + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } @@ -43,11 +34,7 @@ library SafeERC20 { * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ - function safeApprove( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' @@ -55,20 +42,12 @@ library SafeERC20 { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } - function safeIncreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } - function safeDecreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); @@ -77,16 +56,7 @@ library SafeERC20 { } } - function safePermit( - IERC20Permit token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { + function safePermit(IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); diff --git a/contracts/common/SafeERC20Staking.sol b/contracts/common/SafeERC20Staking.sol index 841b87d..c68c891 100644 --- a/contracts/common/SafeERC20Staking.sol +++ b/contracts/common/SafeERC20Staking.sol @@ -19,12 +19,7 @@ import "./Address.sol"; library SafeERC20Staking { using Address for address; - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) internal { + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } @@ -35,11 +30,7 @@ library SafeERC20Staking { * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ - function safeApprove( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' diff --git a/contracts/common/cryptography/ECDSA.sol b/contracts/common/cryptography/ECDSA.sol index ec3d38f..805af22 100644 --- a/contracts/common/cryptography/ECDSA.sol +++ b/contracts/common/cryptography/ECDSA.sol @@ -101,11 +101,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); @@ -116,11 +112,7 @@ library ECDSA { * * _Available since v4.2._ */ - function recover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address) { + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; @@ -132,12 +124,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most @@ -167,12 +154,7 @@ library ECDSA { * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ - function recover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; diff --git a/contracts/common/cryptography/EIP712.sol b/contracts/common/cryptography/EIP712.sol index 42352fd..a56b536 100644 --- a/contracts/common/cryptography/EIP712.sol +++ b/contracts/common/cryptography/EIP712.sol @@ -101,11 +101,7 @@ abstract contract EIP712 { return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); } - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash - ) private view returns (bytes32) { + function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash, bytes32 versionHash) private view returns (bytes32) { return keccak256(abi.encode(typeHash, nameHash, versionHash, getChainID(), address(this))); } } diff --git a/contracts/common/math/BoringMath.sol b/contracts/common/math/BoringMath.sol index d998e8a..fcda8f9 100644 --- a/contracts/common/math/BoringMath.sol +++ b/contracts/common/math/BoringMath.sol @@ -28,7 +28,6 @@ library BoringMath { c = uint224(a); } - function to160(uint256 a) internal pure returns (uint208 c) { require(a <= type(uint160).max, "BoringMath: uint128 Overflow"); c = uint160(a); diff --git a/contracts/common/math/FullMath.sol b/contracts/common/math/FullMath.sol index 1c1c5b0..e8d3238 100644 --- a/contracts/common/math/FullMath.sol +++ b/contracts/common/math/FullMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity 0.8.16; /// @title Contains 512-bit math functions /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision @@ -11,11 +11,7 @@ library FullMath { /// @param denominator The divisor /// @return result The 256-bit result /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv( - uint256 a, - uint256 b, - uint256 denominator - ) internal pure returns (uint256 result) { + function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = a * b // Compute the product mod 2**256 and mod 2**256 - 1 diff --git a/contracts/common/proxy/StakingProxy.sol b/contracts/common/proxy/StakingProxy.sol index 141daf5..43f42ad 100644 --- a/contracts/common/proxy/StakingProxy.sol +++ b/contracts/common/proxy/StakingProxy.sol @@ -1,13 +1,9 @@ // SPDX-License-Identifier: AGPL 3.0 // Original Copyright Aurora // Copyright Fathom 2022 -pragma solidity ^0.8.0; +pragma solidity 0.8.16; import "./transparent/TransparentUpgradeableProxy.sol"; contract StakingProxy is TransparentUpgradeableProxy { - constructor( - address _logic, - address admin_, - bytes memory _data - ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} } diff --git a/contracts/common/proxy/StakingProxyAdmin.sol b/contracts/common/proxy/StakingProxyAdmin.sol index 29d5373..6d3c749 100644 --- a/contracts/common/proxy/StakingProxyAdmin.sol +++ b/contracts/common/proxy/StakingProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL 3.0 // Original Copyright Aurora // Copyright Fathom 2022 -pragma solidity ^0.8.0; +pragma solidity 0.8.16; import "./transparent/ProxyAdmin.sol"; contract StakingProxyAdmin is ProxyAdmin {} diff --git a/contracts/common/proxy/VaultProxy.sol b/contracts/common/proxy/VaultProxy.sol index 950d836..62c7bd8 100644 --- a/contracts/common/proxy/VaultProxy.sol +++ b/contracts/common/proxy/VaultProxy.sol @@ -5,9 +5,5 @@ pragma solidity ^0.8.0; import "./transparent/TransparentUpgradeableProxy.sol"; contract VaultProxy is TransparentUpgradeableProxy { - constructor( - address _logic, - address admin_, - bytes memory _data - ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} } diff --git a/contracts/common/proxy/transparent/ProxyAdmin.sol b/contracts/common/proxy/transparent/ProxyAdmin.sol index 2908d87..3f9e7e6 100644 --- a/contracts/common/proxy/transparent/ProxyAdmin.sol +++ b/contracts/common/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.0; +pragma solidity 0.8.16; import "./TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -71,11 +71,7 @@ contract ProxyAdmin is Ownable { * * - This contract must be the admin of `proxy`. */ - function upgradeAndCall( - TransparentUpgradeableProxy proxy, - address implementation, - bytes memory data - ) public payable virtual onlyOwner { + function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner { proxy.upgradeToAndCall{ value: msg.value }(implementation, data); } } diff --git a/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol index 5a9dc76..9b04d91 100644 --- a/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol @@ -31,11 +31,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ - constructor( - address _logic, - address admin_, - bytes memory _data - ) payable ERC1967Proxy(_logic, _data) { + constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { _changeAdmin(admin_); } diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index 7dc231d..bc491be 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -15,12 +15,33 @@ import "../../common/Strings.sol"; import "./GovernorStructs.sol"; import "./interfaces/IGovernor.sol"; +// solhint-disable not-rely-on-time abstract contract Governor is Context, ERC165, EIP712, IGovernor { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; using SafeCast for uint256; using Strings for *; using Timers for Timers.BlockNumber; + uint256 public maxTargets; + uint256 public proposalTimeDelay; + uint256 public live; + uint256 public proposalLifetime; + mapping(address => bool) public isBlocklisted; + mapping(uint256 => bool) public isConfirmed; + mapping(address => uint256) public nextAcceptableProposalTimestamp; + + mapping(uint256 => ProposalCore) internal _proposals; + mapping(uint256 => string) internal _descriptions; + + address private multiSig; + string private _name; + uint256[] private _proposalIds; + DoubleEndedQueue.Bytes32Deque private _governanceCall; + + uint256 public constant MINIMUM_LIFETIME = 86400; //oneDay + bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); + bytes32 public constant EXTENDED_BALLOT_TYPEHASH = keccak256("ExtendedBallot(uint256 proposalId,uint8 support,string reason,bytes params)"); + event ConfirmProposal(address indexed signer, uint256 indexed proposalId); event RevokeConfirmation(address indexed signer, uint256 indexed proposalId); event ExecuteProposal(address indexed signer, uint256 indexed proposalId); @@ -31,28 +52,37 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { event ProposalLifetimeUpdated(uint256 newProposalLifetime, uint256 oldProposalLifetime); event EmergencyStop(); - bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); - bytes32 public constant EXTENDED_BALLOT_TYPEHASH = keccak256("ExtendedBallot(uint256 proposalId,uint8 support,string reason,bytes params)"); - uint256 public maxTargets; - uint256 public proposalTimeDelay; - string private _name; - uint256[] private proposalIds; - uint256 public live; - - address private multiSig; - uint256 public proposalLifetime; - mapping(address => bool) isBlacklisted; - - mapping(uint256 => ProposalCore) internal _proposals; - mapping(uint256 => string) internal _descriptions; - mapping(uint256 => bool) public isConfirmed; - mapping(address => uint256) public nextAcceptableProposalTimestamp; + error NotLive(); + error OnlyOvernance(); + error OnlyMultiSig(); + error ProposalAlreadyExecuted(); + error ProposalAlreadyConfirmed(); + error ProposalNotActive(); + error ZeroTargets(); + error ZeroDelay(); + error LowLifetime(); + error ZeroAddress(); + error ZeroValue(); + error OnlyExecutor(); + error ProposalNotQueued(); + error InsufficientFunds(); + error FailedToSendEther(); + error InsufficientVotes(); + error ProposerIsBlocklisted(); + error InvalidProposalLength(); + error EmptyProposal(); + error MaxTargetLength(); + error ProposalAlreadyExists(); + error LessThanMinimum(); + error ProposalNotSuccessful(); + error ProposalDelayNotPassed(); + error ProposalExpired(); + error ProposalNotConfirmed(); - DoubleEndedQueue.Bytes32Deque private _governanceCall; - uint256 public constant MINIMUM_LIFETIME = 86400;//oneDay - modifier onlyGovernance() { - require(_msgSender() == _executor(), "onlyGovernance"); + if (_msgSender() != _executor()) { + revert OnlyOvernance(); + } if (_executor() != address(this)) { bytes32 msgDataHash = keccak256(_msgData()); // loop until popping the expected operation - throw if deque is empty (operation not authorized) @@ -62,20 +92,60 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } modifier onlyMultiSig() { - require(_msgSender() == multiSig, "Governor: onlyMultiSig"); + if (_msgSender() != multiSig) { + revert OnlyMultiSig(); + } _; } modifier notExecuted(uint256 _proposalId) { - require(!_proposals[_proposalId].executed, "proposal already executed"); + if (_proposals[_proposalId].executed) { + revert ProposalAlreadyExecuted(); + } _; } modifier notConfirmed(uint256 _proposalId) { - require(!isConfirmed[_proposalId], "proposal already confirmed"); + if (isConfirmed[_proposalId]) { + revert ProposalAlreadyConfirmed(); + } + _; + } + + // solhint-disable code-complexity + modifier verifyProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas + ) { + if (live != 1) { + revert NotLive(); + } + if (getVotes(_msgSender(), block.number - 1) < proposalThreshold()) { + revert InsufficientVotes(); + } + if (isBlocklisted[msg.sender]) { + revert ProposerIsBlocklisted(); + } + _checkNextProposalDelayPassed(msg.sender); + + if (targets.length != values.length) { + revert InvalidProposalLength(); + } + if (targets.length != calldatas.length) { + revert InvalidProposalLength(); + } + if (targets.length == 0) { + revert EmptyProposal(); + } + if (targets.length > maxTargets) { + revert MaxTargetLength(); + } _; } + // solhint-enable code-complexity + constructor( string memory name_, address multiSig_, @@ -83,10 +153,18 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256 proposalTimeDelay_, uint256 proposalLifetime_ ) EIP712(name_, version()) { - require(multiSig_ != address(0), "multiSig zero address"); - require(maxTargets_ != 0, "maxTarget cant be zero"); - require(proposalTimeDelay_ != 0, "proposalTimeDelay cant be zero"); - require(proposalLifetime_ >= MINIMUM_LIFETIME,"lifetime less than minimum"); + if (multiSig_ == address(0)) { + revert ZeroAddress(); + } + if (maxTargets_ == 0) { + revert ZeroTargets(); + } + if (proposalTimeDelay_ == 0) { + revert ZeroDelay(); + } + if (proposalLifetime_ < MINIMUM_LIFETIME) { + revert LowLifetime(); + } _name = name_; multiSig = multiSig_; maxTargets = maxTargets_; @@ -96,7 +174,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } receive() external payable virtual { - require(_executor() == address(this), "Governor, receive(): _executor() != address(this)"); + if (_executor() != address(this)) { + revert OnlyExecutor(); + } } function execute( @@ -104,19 +184,25 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public payable virtual override returns (uint256) { - require(live == 1,"not live"); + ) public payable virtual override returns (uint256) { + if (live != 1) { + revert NotLive(); + } uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); _requireNotExpired(proposalId); _requireConfirmed(proposalId); ProposalState status = state(proposalId); - require(status == ProposalState.Queued, "Governor: proposal not successful"); + if (status != ProposalState.Queued) { + revert ProposalNotQueued(); + } uint256 totalValue = 0; for (uint256 i = 0; i < values.length; i++) { totalValue += values[i]; } - require(msg.value >= totalValue, "msg.value not sufficient"); + if (msg.value < totalValue) { + revert InsufficientFunds(); + } _proposals[proposalId].executed = true; emit ProposalExecuted(proposalId); @@ -126,23 +212,25 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { _afterExecute(proposalId, targets, values, calldatas, descriptionHash); if (msg.value > totalValue) { (bool sent, ) = msg.sender.call{ value: (msg.value - totalValue) }(""); - require(sent, "Failed to send ether"); + if (!sent) { + revert FailedToSendEther(); + } } return proposalId; } function castVote(uint256 proposalId, uint8 support) public virtual override returns (uint256) { - require(live == 1,"not live"); + if (live != 1) { + revert NotLive(); + } address voter = _msgSender(); return _castVote(proposalId, voter, support, ""); } - function castVoteWithReason( - uint256 proposalId, - uint8 support, - string memory reason - ) public virtual override returns (uint256) { - require(live == 1,"not live"); + function castVoteWithReason(uint256 proposalId, uint8 support, string memory reason) public virtual override returns (uint256) { + if (live != 1) { + revert NotLive(); + } address voter = _msgSender(); return _castVote(proposalId, voter, support, reason); } @@ -153,19 +241,17 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { string memory reason, bytes memory params ) public virtual override returns (uint256) { - require(live == 1,"not live"); + if (live != 1) { + revert NotLive(); + } address voter = _msgSender(); return _castVote(proposalId, voter, support, reason, params); } - function castVoteBySig( - uint256 proposalId, - uint8 support, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual override returns (uint256) { - require(live == 1,"not live"); + function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) public virtual override returns (uint256) { + if (live != 1) { + revert NotLive(); + } address voter = ECDSA.recover(_hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support))), v, r, s); return _castVote(proposalId, voter, support, ""); } @@ -179,7 +265,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { bytes32 r, bytes32 s ) public virtual override returns (uint256) { - require(live == 1,"not live"); + if (live != 1) { + revert NotLive(); + } address voter = ECDSA.recover( _hashTypedDataV4(keccak256(abi.encode(EXTENDED_BALLOT_TYPEHASH, proposalId, support, keccak256(bytes(reason)), keccak256(params)))), v, @@ -195,38 +283,17 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256[] memory values, bytes[] memory calldatas, string memory description - ) public virtual override returns (uint256) { - require(live == 1,"not live"); - require(getVotes(_msgSender(), block.number - 1) >= proposalThreshold(), - "Governor: proposer votes below threshold"); - - require(!isBlacklisted[msg.sender],"Proposer is blacklisted"); - _checkNextProposalDelayPassed(msg.sender); - + ) public virtual override verifyProposal(targets, values, calldatas) returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); - require(targets.length == values.length, "Governor: invalid proposal length"); - require(targets.length == calldatas.length, "Governor: invalid proposal length"); - require(targets.length > 0, "Governor: empty proposal"); - require(targets.length <= maxTargets, "Governor: max target length"); - ProposalCore storage proposal = _proposals[proposalId]; - require(proposal.voteStart.isUnset(), "Governor: proposal already exists"); - - uint64 snapshot = block.number.toUint64() + votingDelay().toUint64(); - uint64 deadline = snapshot + votingPeriod().toUint64(); - - proposal.voteStart.setDeadline(snapshot); - proposal.voteEnd.setDeadline(deadline); - proposal.expireTimestamp = block.timestamp + proposalLifetime; - _descriptions[proposalId] = description; - - proposalIds.push(proposalId); + if (proposal.voteStart.isStarted()) { + revert ProposalAlreadyExists(); + } - emit ProposalCreated(proposalId, _msgSender(), targets, values, new string[](targets.length), - calldatas, snapshot, deadline, description); - return proposalId; + return _propose(proposalId, proposal, targets, values, calldatas, description); } + /** * @dev Only Multisig is able to confirm a proposal */ @@ -234,9 +301,12 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { _requireNotExpired(_proposalId); isConfirmed[_proposalId] = true; ProposalState status = state(_proposalId); - require(status == ProposalState.Succeeded || status == ProposalState.Queued, "proposal not successful"); + if (status != ProposalState.Succeeded && status != ProposalState.Queued) { + revert ProposalNotSuccessful(); + } emit ConfirmProposal(msg.sender, _proposalId); } + /** * @dev Only Multisig is able to revoke a proposal confirmation */ @@ -248,17 +318,22 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { /** * @dev Only Multisig can update - */ + */ function updateMultiSig(address newMultiSig) public onlyMultiSig { - require(newMultiSig != address(0), "zero address"); + if (newMultiSig == address(0)) { + revert ZeroAddress(); + } emit MultiSigUpdated(newMultiSig, multiSig); multiSig = newMultiSig; } + /** * @dev Only Multisig can update */ function updateMaxTargets(uint256 newMaxTargets) public onlyMultiSig { - require(newMaxTargets != 0, "zero value"); + if (newMaxTargets == 0) { + revert ZeroValue(); + } emit MaxTargetUpdated(newMaxTargets, maxTargets); maxTargets = newMaxTargets; } @@ -267,42 +342,33 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { * @dev Only Multisig can update */ function updateProposalTimeDelay(uint256 newProposalTimeDelay) public onlyMultiSig { - require(newProposalTimeDelay != 0, "zero value"); + if (newProposalTimeDelay == 0) { + revert ZeroValue(); + } emit ProposalTimeDelayUpdated(newProposalTimeDelay, proposalTimeDelay); proposalTimeDelay = newProposalTimeDelay; } + /** * @dev Only Multisig can update */ function updateProposalLifetime(uint256 newProposalLifetime) public onlyMultiSig { - require(newProposalLifetime>= MINIMUM_LIFETIME, "less than minimum"); + if (newProposalLifetime < MINIMUM_LIFETIME) { + revert LessThanMinimum(); + } emit ProposalLifetimeUpdated(newProposalLifetime, newProposalLifetime); proposalLifetime = newProposalLifetime; } + /** - * @dev Only Multisig can blacklist a an account or unblacklist an account + * @dev Only Multisig can blocklist a an account or unblocklist an account */ - function setBlacklistStatusForProposer(address account, bool blacklistStatus) public onlyMultiSig { - isBlacklisted[account] = blacklistStatus; + function setBlocklistStatusForProposer(address account, bool blocklistStatus) public onlyMultiSig { + isBlocklisted[account] = blocklistStatus; } - function _emergencyStop() internal { - require(live == 1, "not-live"); - live = 0; - emit EmergencyStop(); - } - - function getProposals(uint256 _numIndexes) - public - view - override - returns ( - string[] memory, - string[] memory, - string[] memory - ) - { - uint256 len = proposalIds.length; + function getProposals(uint256 _numIndexes) public view override returns (string[] memory, string[] memory, string[] memory) { + uint256 len = _proposalIds.length; if (len == 0) { string[] memory a; @@ -316,49 +382,8 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return _getProposals1(_numIndexes); } - function _getProposals1(uint256 _numIndexes) - internal - view - returns ( - string[] memory, - string[] memory, - string[] memory - ) - { - string[] memory _statusses = new string[](_numIndexes); - string[] memory _descriptionsArray = new string[](_numIndexes); - string[] memory _proposalIds = new string[](_numIndexes); - - uint256 counter = proposalIds.length; - - uint256 indexCounter = _numIndexes - 1; - - if (_numIndexes >= counter) { - indexCounter = counter - 1; - } - - while (indexCounter >= 0) { - uint256 _currentPropId = proposalIds[counter - 1]; - _proposalIds[indexCounter] = string(_currentPropId.toString()); - _descriptionsArray[indexCounter] = _descriptions[_currentPropId]; - _statusses[indexCounter] = (uint8(state(_currentPropId))).toString(); - - if (counter - 1 == 0) { - break; - } - if (indexCounter == 0) { - break; - } - - counter--; - indexCounter--; - } - - return (_proposalIds, _descriptionsArray, _statusses); - } - function getProposalIds() public view override returns (uint256[] memory) { - return proposalIds; + return _proposalIds; } function getDescription(uint256 _proposalId) public view override returns (string memory) { @@ -369,11 +394,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return _getVotes(account, blockNumber, _defaultParams()); } - function getVotesWithParams( - address account, - uint256 blockNumber, - bytes memory params - ) public view virtual override returns (uint256) { + function getVotesWithParams(address account, uint256 blockNumber, bytes memory params) public view virtual override returns (uint256) { return _getVotes(account, blockNumber, params); } @@ -410,7 +431,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256 snapshot = proposalSnapshot(proposalId); if (snapshot == 0) { - revert("Governor: unknown proposal id"); + revert("unknown proposal id"); } if (snapshot >= block.number) { @@ -442,7 +463,6 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return 0; } - function hashProposal( address[] memory targets, uint256[] memory values, @@ -452,16 +472,40 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); } - function _countVote( + function _propose( uint256 proposalId, - address account, - uint8 support, - uint256 weight, - bytes memory params - ) internal virtual; + ProposalCore storage proposal, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) internal virtual returns (uint256) { + uint64 snapshot = block.number.toUint64() + votingDelay().toUint64(); + uint64 deadline = snapshot + votingPeriod().toUint64(); + + proposal.voteStart.setDeadline(snapshot); + proposal.voteEnd.setDeadline(deadline); + proposal.expireTimestamp = block.timestamp + proposalLifetime; + _descriptions[proposalId] = description; + + _proposalIds.push(proposalId); + + emit ProposalCreated(proposalId, _msgSender(), targets, values, new string[](targets.length), calldatas, snapshot, deadline, description); + return proposalId; + } + + function _emergencyStop() internal { + if (live != 1) { + revert NotLive(); + } + live = 0; + emit EmergencyStop(); + } + + function _countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes memory params) internal virtual; function _execute( - uint256, /*proposalId*/ + uint256 /*proposalId*/, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, @@ -474,9 +518,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } function _beforeExecute( - uint256, /* proposalId */ + uint256 /* proposalId */, address[] memory targets, - uint256[] memory, /* values */ + uint256[] memory /* values */, bytes[] memory calldatas, bytes32 /*descriptionHash*/ ) internal virtual { @@ -490,10 +534,10 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } function _afterExecute( - uint256, /* proposalId */ - address[] memory, /* targets */ - uint256[] memory, /* values */ - bytes[] memory, /* calldatas */ + uint256 /* proposalId */, + address[] memory /* targets */, + uint256[] memory /* values */, + bytes[] memory /* calldatas */, bytes32 /*descriptionHash*/ ) internal virtual { if (_executor() != address(this)) { @@ -512,10 +556,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); ProposalState status = state(proposalId); - require( - status != ProposalState.Canceled && status != ProposalState.Expired && status != ProposalState.Executed, - "Governor: proposal not active" - ); + if (status == ProposalState.Canceled || status == ProposalState.Expired || status == ProposalState.Executed) { + revert ProposalNotActive(); + } _proposals[proposalId].canceled = true; emit ProposalCanceled(proposalId); @@ -523,12 +566,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return proposalId; } - function _castVote( - uint256 proposalId, - address account, - uint8 support, - string memory reason - ) internal virtual returns (uint256) { + function _castVote(uint256 proposalId, address account, uint8 support, string memory reason) internal virtual returns (uint256) { return _castVote(proposalId, account, support, reason, _defaultParams()); } @@ -540,7 +578,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { bytes memory params ) internal virtual returns (uint256) { ProposalCore storage proposal = _proposals[proposalId]; - require(state(proposalId) == ProposalState.Active, "Governor: vote inactive"); + if (state(proposalId) != ProposalState.Active) { + revert ProposalNotActive(); + } uint256 weight = _getVotes(account, proposal.voteStart.getDeadline(), params); _countVote(proposalId, account, support, weight, params); @@ -554,25 +594,57 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return weight; } - function _getProposalsAll(uint256 len) - internal - view - returns ( - string[] memory, - string[] memory, - string[] memory - ) - { - string[] memory _statusses = new string[](len); - string[] memory _descriptionsArray = new string[](len); - string[] memory _proposalIds = new string[](len); + function _checkNextProposalDelayPassed(address account) internal { + if (block.timestamp <= nextAcceptableProposalTimestamp[account]) { + revert ProposalDelayNotPassed(); + } + nextAcceptableProposalTimestamp[account] = block.timestamp + proposalTimeDelay; + } + + function _getProposals1(uint256 _numIndexes) internal view returns (string[] memory, string[] memory, string[] memory) { + string[] memory statuses = new string[](_numIndexes); + string[] memory descriptionsArray = new string[](_numIndexes); + string[] memory proposalIds = new string[](_numIndexes); + + uint256 counter = _proposalIds.length; + + uint256 indexCounter = _numIndexes - 1; + + if (_numIndexes >= counter) { + indexCounter = counter - 1; + } + + while (indexCounter >= 0) { + uint256 _currentPropId = _proposalIds[counter - 1]; + proposalIds[indexCounter] = string(_currentPropId.toString()); + descriptionsArray[indexCounter] = _descriptions[_currentPropId]; + statuses[indexCounter] = (uint8(state(_currentPropId))).toString(); + + if (counter - 1 == 0) { + break; + } + if (indexCounter == 0) { + break; + } + + counter--; + indexCounter--; + } + + return (proposalIds, descriptionsArray, statuses); + } + + function _getProposalsAll(uint256 len) internal view returns (string[] memory, string[] memory, string[] memory) { + string[] memory statuses = new string[](len); + string[] memory descriptionsArray = new string[](len); + string[] memory proposalIds = new string[](len); uint256 i = len - 1; while (i >= 0) { - uint256 _proposalId = proposalIds[i]; - _proposalIds[i] = _proposalId.toString(); - _descriptionsArray[i] = _descriptions[_proposalId]; - _statusses[i] = (uint8(state(_proposalId))).toString(); + uint256 _proposalId = _proposalIds[i]; + proposalIds[i] = _proposalId.toString(); + descriptionsArray[i] = _descriptions[_proposalId]; + statuses[i] = (uint8(state(_proposalId))).toString(); if (i == 0) { break; @@ -580,30 +652,22 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { i--; } - return (_proposalIds, _descriptionsArray, _statusses); + return (proposalIds, descriptionsArray, statuses); } - function _getProposals(uint256 _numIndexes, uint256 len) - internal - view - returns ( - string[] memory, - string[] memory, - string[] memory - ) - { - string[] memory _statusses = new string[](_numIndexes); - string[] memory _descriptionsArray = new string[](_numIndexes); - string[] memory _proposalIds = new string[](_numIndexes); + function _getProposals(uint256 _numIndexes, uint256 len) internal view returns (string[] memory, string[] memory, string[] memory) { + string[] memory statuses = new string[](_numIndexes); + string[] memory descriptionsArray = new string[](_numIndexes); + string[] memory proposalIds = new string[](_numIndexes); // uint _lb = len - _numIndexes; uint256 i = _numIndexes; while (i > 0) { - uint256 _proposalId = proposalIds[len - 1 - i]; - _proposalIds[i - 1] = _proposalId.toString(); - _descriptionsArray[i - 1] = _descriptions[_proposalId]; - _statusses[i - 1] = (uint8(state(_proposalId))).toString(); + uint256 _proposalId = _proposalIds[len - 1 - i]; + proposalIds[i - 1] = _proposalId.toString(); + descriptionsArray[i - 1] = _descriptions[_proposalId]; + statuses[i - 1] = (uint8(state(_proposalId))).toString(); if (i == 0) { break; @@ -611,35 +675,30 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { i--; } - return (_proposalIds, _descriptionsArray, _statusses); + return (proposalIds, descriptionsArray, statuses); } function _requireConfirmed(uint256 _proposalId) internal view { - require(isConfirmed[_proposalId], "proposal not confirmed"); + if (!isConfirmed[_proposalId]) { + revert ProposalNotConfirmed(); + } } function _requireNotExpired(uint256 _proposalId) internal view { - require(_proposals[_proposalId].expireTimestamp >= block.timestamp,"proposal expired"); + if (_proposals[_proposalId].expireTimestamp < block.timestamp) { + revert ProposalExpired(); + } } function _executor() internal view virtual returns (address) { return address(this); } - function _checkNextProposalDelayPassed(address account) internal { - require(block.timestamp > nextAcceptableProposalTimestamp[account], "Can submit only after certain delay"); - nextAcceptableProposalTimestamp[account] = block.timestamp + proposalTimeDelay; - } - function _quorumReached(uint256 proposalId) internal view virtual returns (bool); function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); - function _getVotes( - address account, - uint256 blockNumber, - bytes memory params - ) internal view virtual returns (uint256); + function _getVotes(address account, uint256 blockNumber, bytes memory params) internal view virtual returns (uint256); function _defaultParams() internal view virtual returns (bytes memory) { return ""; diff --git a/contracts/dao/governance/MainTokenGovernor.sol b/contracts/dao/governance/MainTokenGovernor.sol index a79edcf..28d172c 100644 --- a/contracts/dao/governance/MainTokenGovernor.sol +++ b/contracts/dao/governance/MainTokenGovernor.sol @@ -4,6 +4,9 @@ pragma solidity 0.8.16; import "./Governor.sol"; +import "./interfaces/IRelay.sol"; +import "./interfaces/ISupportingTokens.sol"; +import "./interfaces/IEmergencyStop.sol"; import "./extensions/GovernorSettings.sol"; import "./extensions/GovernorCountingSimple.sol"; import "./extensions/GovernorVotes.sol"; @@ -13,6 +16,9 @@ import "../tokens/ERC20/IERC20.sol"; import "../../common/SafeERC20.sol"; contract MainTokenGovernor is + IRelay, + ISupportingTokens, + IEmergencyStop, Governor, GovernorSettings, GovernorCountingSimple, @@ -24,6 +30,9 @@ contract MainTokenGovernor is mapping(address => bool) public isSupportedToken; address[] public listOfSupportedTokens; + error TokenSupported(); + error TokenUnsupported(); + constructor( IVotes _token, TimelockController _timelock, @@ -34,13 +43,41 @@ contract MainTokenGovernor is uint256 _proposalTimeDelay, uint256 _proposalLifetime ) - Governor("MainTokenGovernor", _multiSig, 20, _proposalTimeDelay,_proposalLifetime) + Governor("MainTokenGovernor", _multiSig, 20, _proposalTimeDelay, _proposalLifetime) GovernorSettings(_initialVotingDelay, _votingPeriod, _initialProposalThreshold) GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * in a governance proposal to recover tokens that was sent to the governor contract by mistake. + * Note that if the executor is simply the governor itself, use of `relay` is redundant. + */ + function relayERC20(address target, bytes calldata data) external virtual override onlyGovernance { + if (!isSupportedToken[target]) { + revert TokenUnsupported(); + } + (bool success, bytes memory returndata) = target.call(data); + Address.verifyCallResult(success, returndata, "empty revert"); + } + + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * in a governance proposal to recover Ether that was sent to the governor contract by mistake. + * Note that if the executor is simply the governor itself, use of `relay` is redundant. + */ + function relayNativeToken(address target, uint256 value, bytes calldata data) external payable virtual override onlyGovernance { + if (isSupportedToken[target]) { + revert TokenSupported(); + } + (bool success, bytes memory returndata) = target.call{ value: value }(data); + Address.verifyCallResult(success, returndata, "empty revert"); + } + function propose( address[] memory targets, uint256[] memory values, @@ -49,6 +86,7 @@ contract MainTokenGovernor is ) public override(Governor, IGovernor) returns (uint256) { return super.propose(targets, values, calldatas, description); } + /** * @dev Cancelling of proposal can be done only through Multisig */ @@ -61,54 +99,33 @@ contract MainTokenGovernor is return _cancel(targets, values, calldatas, descriptionHash); } - function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { - return super.proposalThreshold(); - } - - function supportsInterface(bytes4 interfaceId) public view override(Governor, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) { - return super.votingDelay(); - } - - function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) { - return super.votingPeriod(); - } - - function quorum(uint256 blockNumber) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { - return super.quorum(blockNumber); - } - - function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { - return super.state(proposalId); - } - /** * @dev A multisig can stop this contract. Once stopped we will have to migrate. * Once this function is called, the contract cannot be made live again. */ - function emergencyStop() public onlyMultiSig{ + function emergencyStop() public override onlyMultiSig { _emergencyStop(); - for(uint i = 0; i < listOfSupportedTokens.length;i++){ + for (uint i = 0; i < listOfSupportedTokens.length; i++) { address _token = listOfSupportedTokens[i]; uint256 balanceInContract = IERC20(_token).balanceOf(address(this)); - if(balanceInContract > 0){ + if (balanceInContract > 0) { IERC20(_token).safeTransfer(msg.sender, balanceInContract); - } + } + } + if (address(this).balance > 0) { + (bool sent, ) = msg.sender.call{ value: (address(this).balance) }(""); + if (!sent) { + revert FailedToSendEther(); + } } - if (address(this).balance > 0){ - (bool sent,) = msg.sender.call{ value: (address(this).balance) }(""); - require(sent, "Failed to send ether"); - } } + /** * @dev Adds supporting tokens so that if there are tokens then it can be transferred * Only Governance is able to access this function. * It has to go through proposal and successful voting for execution. - */ - function addSupportingToken(address _token) public onlyGovernance { + */ + function addSupportingToken(address _token) public override onlyGovernance { _addSupportedToken(_token); } @@ -116,19 +133,47 @@ contract MainTokenGovernor is * @dev Removes supporting tokens * Only Governance is able to access this function. * It has to go through proposal and successful voting for execution. - */ - function removeSupportingToken(address _token) public onlyGovernance { + */ + function removeSupportingToken(address _token) public override onlyGovernance { _removeSupportingToken(_token); } + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function supportsInterface(bytes4 interfaceId) public view override(Governor, GovernorTimelockControl) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) { + return super.votingDelay(); + } + + function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) { + return super.votingPeriod(); + } + + function quorum(uint256 blockNumber) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + function _addSupportedToken(address _token) internal { - require(!isSupportedToken[_token], "Token already exists"); + if (isSupportedToken[_token]) { + revert TokenSupported(); + } isSupportedToken[_token] = true; listOfSupportedTokens.push(_token); } function _removeSupportingToken(address _token) internal { - require(isSupportedToken[_token], "Token already doesnt exist"); + if (!isSupportedToken[_token]) { + revert TokenUnsupported(); + } isSupportedToken[_token] = false; for (uint256 i = 0; i < listOfSupportedTokens.length; i++) { if (listOfSupportedTokens[i] == _token) { @@ -138,38 +183,6 @@ contract MainTokenGovernor is } listOfSupportedTokens.pop(); } - - - /** - * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor - * is some contract other than the governor itself, like when using a timelock, this function can be invoked - * in a governance proposal to recover tokens that was sent to the governor contract by mistake. - * Note that if the executor is simply the governor itself, use of `relay` is redundant. - */ - function relayERC20( - address target, - bytes calldata data - ) external virtual onlyGovernance { - require(isSupportedToken[target], "token not supported"); - (bool success, bytes memory returndata) = target.call(data); - Address.verifyCallResult(success, returndata, "reverted without message"); - } - - /** - * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor - * is some contract other than the governor itself, like when using a timelock, this function can be invoked - * in a governance proposal to recover Ether that was sent to the governor contract by mistake. - * Note that if the executor is simply the governor itself, use of `relay` is redundant. - */ - function relayNativeToken( - address target, - uint256 value, - bytes calldata data - ) external payable virtual onlyGovernance { - require(!isSupportedToken[target],"cant relay native token to supported token"); - (bool success, bytes memory returndata) = target.call{ value: value }(data); - Address.verifyCallResult(success, returndata, "reverted without message"); - } function _execute( uint256 proposalId, @@ -178,7 +191,9 @@ contract MainTokenGovernor is bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) { - require(isConfirmed[proposalId], "Proposal not confirmed"); + if (!isConfirmed[proposalId]) { + revert ProposalNotConfirmed(); + } super._execute(proposalId, targets, values, calldatas, descriptionHash); } diff --git a/contracts/dao/governance/TimelockController.sol b/contracts/dao/governance/TimelockController.sol index 0a68c71..8028934 100644 --- a/contracts/dao/governance/TimelockController.sol +++ b/contracts/dao/governance/TimelockController.sol @@ -9,6 +9,7 @@ import "../../common/Address.sol"; import "./interfaces/ITimelockController.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +// solhint-disable not-rely-on-time contract TimelockController is AccessControl, Initializable, ITimelockController { bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE"); bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); @@ -35,6 +36,18 @@ contract TimelockController is AccessControl, Initializable, ITimelockController event ExecuteTransaction(address indexed owner, bool indexed success, bytes data); + error ZeroValue(); + error ZeroAddress(); + error LengthMismatch(); + error OperationNotPending(); + error InsufficientValue(); + error FailedToSendEther(); + error NotTimelock(); + error OperationNotReady(); + error OperationAlreadyScheduled(); + error InsufficientDelay(); + error MissingDependency(); + /** * @dev Modifier to make a function callable only by a certain role. In * addition to checking the sender's role, `address(0)` 's role is also @@ -48,14 +61,16 @@ contract TimelockController is AccessControl, Initializable, ITimelockController _; } - function initialize( - uint256 minDelay, - address admin, - address[] memory proposers, - address[] memory executors - ) public override initializer { - require(minDelay != 0, "minDelay should be greater than zero"); - require(admin != address(0), "admin should not be zero address"); + // solhint-disable-next-line comprehensive-interface + receive() external payable {} + + function initialize(uint256 minDelay, address admin, address[] memory proposers, address[] memory executors) public override initializer { + if (minDelay == 0) { + revert ZeroValue(); + } + if (admin == address(0)) { + revert ZeroAddress(); + } _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE); _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE); _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE); @@ -67,13 +82,17 @@ contract TimelockController is AccessControl, Initializable, ITimelockController _grantRole(DEFAULT_ADMIN_ROLE, admin); for (uint256 i = 0; i < proposers.length; ++i) { - require(proposers[i] != address(0), "proposer should not be zero address"); + if (proposers[i] == address(0)) { + revert ZeroAddress(); + } _setupRole(PROPOSER_ROLE, proposers[i]); _setupRole(CANCELLER_ROLE, proposers[i]); } for (uint256 i = 0; i < executors.length; ++i) { - require(executors[i] != address(0), "executor should not be zero address"); + if (executors[i] == address(0)) { + revert ZeroAddress(); + } _setupRole(EXECUTOR_ROLE, executors[i]); } @@ -81,8 +100,6 @@ contract TimelockController is AccessControl, Initializable, ITimelockController emit MinDelayChange(0, minDelay); } - receive() external payable {} - function schedule( address target, uint256 value, @@ -90,7 +107,7 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes32 predecessor, bytes32 salt, uint256 delay - ) public virtual onlyRole(PROPOSER_ROLE) { + ) public virtual override onlyRole(PROPOSER_ROLE) { bytes32 id = hashOperation(target, value, data, predecessor, salt); _schedule(id, delay); emit CallScheduled(id, 0, target, value, data, predecessor, delay); @@ -103,9 +120,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes32 predecessor, bytes32 salt, uint256 delay - ) public virtual onlyRole(PROPOSER_ROLE) { - require(targets.length == values.length, "TimelockController: length mismatch"); - require(targets.length == payloads.length, "TimelockController: length mismatch"); + ) public virtual override onlyRole(PROPOSER_ROLE) { + if (targets.length != values.length || targets.length != payloads.length) { + revert LengthMismatch(); + } bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _schedule(id, delay); @@ -114,8 +132,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController } } - function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) { - require(isOperationPending(id), "TimelockController: operation cannot be cancelled"); + function cancel(bytes32 id) public virtual override onlyRole(CANCELLER_ROLE) { + if (!isOperationPending(id)) { + revert OperationNotPending(); + } delete _timestamps[id]; emit Cancelled(id); @@ -127,16 +147,20 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes memory payload, bytes32 predecessor, bytes32 salt - ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { + ) public payable virtual override onlyRoleOrOpenRole(EXECUTOR_ROLE) { bytes32 id = hashOperation(target, value, payload, predecessor, salt); - require(msg.value >= value, "execute: msg.value insufficient sent"); + if (msg.value < value) { + revert InsufficientValue(); + } _beforeCall(id, predecessor); _execute(target, value, payload); emit CallExecuted(id, 0, target, value, payload); _afterCall(id); if (msg.value > value) { (bool sent, ) = msg.sender.call{ value: (msg.value - value) }(""); - require(sent, "Failed to send ether"); + if (!sent) { + revert FailedToSendEther(); + } } } @@ -146,9 +170,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes[] memory payloads, bytes32 predecessor, bytes32 salt - ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { - require(targets.length == values.length, "TimelockController: length mismatch"); - require(targets.length == payloads.length, "TimelockController: length mismatch"); + ) public payable virtual override onlyRoleOrOpenRole(EXECUTOR_ROLE) { + if (targets.length != values.length || targets.length != payloads.length) { + revert LengthMismatch(); + } bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); uint256 totalValue; @@ -162,46 +187,62 @@ contract TimelockController is AccessControl, Initializable, ITimelockController emit CallExecuted(id, i, target, value, payload); } _afterCall(id); - require(msg.value >= totalValue,"executeBatch: msg.value insufficient sent"); - if(msg.value > totalValue){ + if (msg.value < totalValue) { + revert InsufficientValue(); + } + if (msg.value > totalValue) { (bool sent, ) = msg.sender.call{ value: (msg.value - totalValue) }(""); - require(sent, "Failed to send ether"); + if (!sent) { + revert FailedToSendEther(); + } } } - function updateDelay(uint256 newDelay) public virtual { - require(msg.sender == address(this), "TimelockController: caller must be timelock"); - require(newDelay > 0, "new delay should be greater than zero"); + function updateDelay(uint256 newDelay) public virtual override { + if (msg.sender != address(this)) { + revert NotTimelock(); + } + if (newDelay == 0) { + revert ZeroValue(); + } emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; } + function grantRoleByAdmin(bytes32 role, address account) public override onlyRole(DEFAULT_ADMIN_ROLE) { + _grantRole(role, account); + } + + function revokeRoleByAdmin(bytes32 role, address account) public override onlyRole(DEFAULT_ADMIN_ROLE) { + _revokeRole(role, account); + } + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return super.supportsInterface(interfaceId); } - function isOperation(bytes32 id) public view virtual returns (bool registered) { + function isOperation(bytes32 id) public view virtual override returns (bool registered) { return getTimestamp(id) > 0; } - function isOperationPending(bytes32 id) public view virtual returns (bool pending) { + function isOperationPending(bytes32 id) public view virtual override returns (bool pending) { return getTimestamp(id) > _DONE_TIMESTAMP; } - function isOperationReady(bytes32 id) public view virtual returns (bool ready) { + function isOperationReady(bytes32 id) public view virtual override returns (bool ready) { uint256 timestamp = getTimestamp(id); return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp; } - function isOperationDone(bytes32 id) public view virtual returns (bool done) { + function isOperationDone(bytes32 id) public view virtual override returns (bool done) { return getTimestamp(id) == _DONE_TIMESTAMP; } - function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) { + function getTimestamp(bytes32 id) public view virtual override returns (uint256 timestamp) { return _timestamps[id]; } - function getMinDelay() public view virtual returns (uint256 duration) { + function getMinDelay() public view virtual override returns (uint256 duration) { return _minDelay; } @@ -211,7 +252,7 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes memory data, bytes32 predecessor, bytes32 salt - ) public pure virtual returns (bytes32 hash) { + ) public pure virtual override returns (bytes32 hash) { return keccak256(abi.encode(target, value, data, predecessor, salt)); } @@ -221,40 +262,38 @@ contract TimelockController is AccessControl, Initializable, ITimelockController bytes[] memory payloads, bytes32 predecessor, bytes32 salt - ) public pure virtual returns (bytes32 hash) { + ) public pure virtual override returns (bytes32 hash) { return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); } - function grantRoleByAdmin(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) { - _grantRole(role, account); - } - - function revokeRoleByAdmin(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) { - _revokeRole(role, account); - } - - function _execute( - address target, - uint256 value, - bytes memory data - ) internal virtual { + function _execute(address target, uint256 value, bytes memory data) internal virtual { (bool success, ) = target.call{ value: value }(data); - emit ExecuteTransaction(msg.sender,success, data); + emit ExecuteTransaction(msg.sender, success, data); } function _afterCall(bytes32 id) private { - require(isOperationReady(id), "TimelockController: operation is not ready"); + if (!isOperationReady(id)) { + revert OperationNotReady(); + } _timestamps[id] = _DONE_TIMESTAMP; } function _schedule(bytes32 id, uint256 delay) private { - require(!isOperation(id), "TimelockController: operation already scheduled"); - require(delay >= getMinDelay(), "TimelockController: insufficient delay"); + if (isOperation(id)) { + revert OperationAlreadyScheduled(); + } + if (delay < getMinDelay()) { + revert InsufficientDelay(); + } _timestamps[id] = block.timestamp + delay; } function _beforeCall(bytes32 id, bytes32 predecessor) private view { - require(isOperationReady(id), "TimelockController: operation is not ready"); - require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency"); + if (!isOperationPending(id)) { + revert OperationNotPending(); + } + if (predecessor != bytes32(0) && !isOperationDone(predecessor)) { + revert MissingDependency(); + } } } diff --git a/contracts/dao/governance/extensions/GovernorCountingSimple.sol b/contracts/dao/governance/extensions/GovernorCountingSimple.sol index 640ba8b..441e2e9 100644 --- a/contracts/dao/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/dao/governance/extensions/GovernorCountingSimple.sol @@ -22,20 +22,14 @@ abstract contract GovernorCountingSimple is Governor { mapping(uint256 => ProposalVote) private _proposalVotes; - function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + error VoteCasted(); + error WrongVoteType(); + + function hasVoted(uint256 proposalId, address account) external view virtual override returns (bool) { return _proposalVotes[proposalId].hasVoted[account]; } - function proposalVotes(uint256 proposalId) - public - view - virtual - returns ( - uint256 againstVotes, - uint256 forVotes, - uint256 abstainVotes - ) - { + function proposalVotes(uint256 proposalId) external view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { ProposalVote storage proposalvote = _proposalVotes[proposalId]; return (proposalvote.againstVotes, proposalvote.forVotes, proposalvote.abstainVotes); } @@ -49,7 +43,9 @@ abstract contract GovernorCountingSimple is Governor { ) internal virtual override { ProposalVote storage proposalvote = _proposalVotes[proposalId]; - require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already cast"); + if (proposalvote.hasVoted[account]) { + revert VoteCasted(); + } proposalvote.hasVoted[account] = true; if (support == uint8(VoteType.Against)) { @@ -59,7 +55,7 @@ abstract contract GovernorCountingSimple is Governor { } else if (support == uint8(VoteType.Abstain)) { proposalvote.abstainVotes += weight; } else { - revert("GovernorVotingSimple: invalid value for enum VoteType"); + revert WrongVoteType(); } } diff --git a/contracts/dao/governance/extensions/GovernorSettings.sol b/contracts/dao/governance/extensions/GovernorSettings.sol index 3fa9d15..be1a1d9 100644 --- a/contracts/dao/governance/extensions/GovernorSettings.sol +++ b/contracts/dao/governance/extensions/GovernorSettings.sol @@ -15,11 +15,10 @@ abstract contract GovernorSettings is Governor { event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod); event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold); - constructor( - uint256 initialVotingDelay, - uint256 initialVotingPeriod, - uint256 initialProposalThreshold - ) { + error ZeroVotePeriod(); + error ZeroThreshold(); + + constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold) { _setVotingDelay(initialVotingDelay); _setVotingPeriod(initialVotingPeriod); _setProposalThreshold(initialProposalThreshold); @@ -28,21 +27,21 @@ abstract contract GovernorSettings is Governor { /** * @dev Has to go through proposals and successful voting to update by Governance */ - function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance { + function setVotingDelay(uint256 newVotingDelay) external virtual onlyGovernance { _setVotingDelay(newVotingDelay); } /** * @dev Has to go through proposals and successful voting to update by Governance */ - function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance { + function setVotingPeriod(uint256 newVotingPeriod) external virtual onlyGovernance { _setVotingPeriod(newVotingPeriod); } /** * @dev Has to go through proposals and successful voting to update by Governance */ - function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance { + function setProposalThreshold(uint256 newProposalThreshold) external virtual onlyGovernance { _setProposalThreshold(newProposalThreshold); } @@ -64,14 +63,18 @@ abstract contract GovernorSettings is Governor { } function _setVotingPeriod(uint256 newVotingPeriod) internal virtual { - require(newVotingPeriod > 0, "GovernorSettings: voting period too low"); + if (newVotingPeriod == 0) { + revert ZeroVotePeriod(); + } emit VotingPeriodSet(_votingPeriod, newVotingPeriod); _votingPeriod = newVotingPeriod; } function _setProposalThreshold(uint256 newProposalThreshold) internal virtual { emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); - require(newProposalThreshold > 0, "_setProposalThreshold: Threshold for proposal cant be zero"); + if (newProposalThreshold == 0) { + revert ZeroThreshold(); + } _proposalThreshold = newProposalThreshold; } } diff --git a/contracts/dao/governance/extensions/GovernorTimelockControl.sol b/contracts/dao/governance/extensions/GovernorTimelockControl.sol index 1f4053e..65ea4ec 100644 --- a/contracts/dao/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/dao/governance/extensions/GovernorTimelockControl.sol @@ -14,8 +14,14 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { mapping(uint256 => bool) private isProposalExecuted; event TimelockChange(address oldTimelock, address newTimelock); + error NotConfirmed(); + error NotSuccessful(); + error AlreadyExecuted(); + constructor(TimelockController timelockAddress) { - require(address(timelockAddress) != address(0), "zero address"); + if (address(timelockAddress) == address(0)) { + revert ZeroAddress(); + } _updateTimelock(timelockAddress); } @@ -28,14 +34,18 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { * @notice The proposal must be confirmed by multisig before it can be queued */ function queue( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, bytes32 descriptionHash - ) public virtual override returns (uint256) { + ) external virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - require(isConfirmed[proposalId], "queue: not confirmed by multisig"); - require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful"); + if (!isConfirmed[proposalId]) { + revert NotConfirmed(); + } + if (state(proposalId) != ProposalState.Succeeded) { + revert NotSuccessful(); + } uint256 delay = _timelock.getMinDelay(); _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash); @@ -47,6 +57,15 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { return proposalId; } + function timelock() external view virtual override returns (address) { + return address(_timelock); + } + + function proposalEta(uint256 proposalId) external view virtual override returns (uint256) { + uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]); + return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value + } + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, Governor) returns (bool) { return interfaceId == type(IGovernorTimelock).interfaceId || super.supportsInterface(interfaceId); } @@ -70,15 +89,6 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } } - function timelock() public view virtual override returns (address) { - return address(_timelock); - } - - function proposalEta(uint256 proposalId) public view virtual override returns (uint256) { - uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]); - return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value - } - function _execute( uint256 proposalId, address[] memory targets, @@ -86,7 +96,9 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes[] memory calldatas, bytes32 descriptionHash ) internal virtual override { - require(!isProposalExecuted[proposalId], "already executed"); + if (isProposalExecuted[proposalId]) { + revert AlreadyExecuted(); + } _timelock.executeBatch{ value: msg.value }(targets, values, calldatas, 0, descriptionHash); isProposalExecuted[proposalId] = true; } @@ -115,7 +127,9 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } function _updateTimelock(TimelockController newTimelock) private { - require(address(newTimelock) != address(0), "zero address"); + if (address(newTimelock) == address(0)) { + revert ZeroAddress(); + } emit TimelockChange(address(_timelock), address(newTimelock)); _timelock = newTimelock; } diff --git a/contracts/dao/governance/extensions/GovernorVotes.sol b/contracts/dao/governance/extensions/GovernorVotes.sol index 24420d9..83cc31d 100644 --- a/contracts/dao/governance/extensions/GovernorVotes.sol +++ b/contracts/dao/governance/extensions/GovernorVotes.sol @@ -11,15 +11,13 @@ abstract contract GovernorVotes is Governor { IVotes public immutable token; constructor(IVotes tokenAddress) { - require(address(tokenAddress) != address(0), "tokenAddress cant be zero address"); + if (address(tokenAddress) == address(0)) { + revert ZeroAddress(); + } token = tokenAddress; } - function _getVotes( - address account, - uint256 blockNumber, - bytes memory /*params*/ - ) internal view virtual override returns (uint256) { + function _getVotes(address account, uint256 blockNumber, bytes memory /*params*/) internal view virtual override returns (uint256) { return token.getPastVotes(account, blockNumber); } } diff --git a/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol index 25e5939..ce81015 100644 --- a/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/dao/governance/extensions/GovernorVotesQuorumFraction.sol @@ -12,6 +12,9 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { uint256 public constant MINIMUM_QUORUM_NUMERATOR = uint256(2); event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); + error QuorumNumeratorOverflow(); + error QuorumNumeratorUnderflow(); + /** * @dev Initialize quorum as a fraction of the token's total supply. * @@ -53,8 +56,12 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { } function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { - require(newQuorumNumerator <= quorumDenominator(), "quorumNumerator over quorumDenominator"); - require(newQuorumNumerator >= MINIMUM_QUORUM_NUMERATOR, "less than Minimum"); + if (newQuorumNumerator > quorumDenominator()) { + revert QuorumNumeratorOverflow(); + } + if (newQuorumNumerator < MINIMUM_QUORUM_NUMERATOR) { + revert QuorumNumeratorUnderflow(); + } uint256 oldQuorumNumerator = _quorumNumerator; _quorumNumerator = newQuorumNumerator; diff --git a/contracts/dao/governance/extensions/IGovernorTimelock.sol b/contracts/dao/governance/extensions/IGovernorTimelock.sol index 68a2dbf..48f856e 100644 --- a/contracts/dao/governance/extensions/IGovernorTimelock.sol +++ b/contracts/dao/governance/extensions/IGovernorTimelock.sol @@ -10,13 +10,13 @@ abstract contract IGovernorTimelock is IGovernor { event ProposalQueued(uint256 proposalId, uint256 eta); function queue( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, bytes32 descriptionHash - ) public virtual returns (uint256 proposalId); + ) external virtual returns (uint256 proposalId); - function timelock() public view virtual returns (address); + function timelock() external view virtual returns (address); - function proposalEta(uint256 proposalId) public view virtual returns (uint256); + function proposalEta(uint256 proposalId) external view virtual returns (uint256); } diff --git a/contracts/dao/governance/extensions/IVotes.sol b/contracts/dao/governance/extensions/IVotes.sol index 9e89bd9..9e4dd6f 100644 --- a/contracts/dao/governance/extensions/IVotes.sol +++ b/contracts/dao/governance/extensions/IVotes.sol @@ -10,14 +10,7 @@ interface IVotes { function delegate(address delegatee) external; - function delegateBySig( - address delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external; + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; function getVotes(address account) external view returns (uint256); diff --git a/contracts/dao/governance/interfaces/IEmergencyStop.sol b/contracts/dao/governance/interfaces/IEmergencyStop.sol new file mode 100644 index 0000000..eb89066 --- /dev/null +++ b/contracts/dao/governance/interfaces/IEmergencyStop.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 +pragma solidity 0.8.16; + +interface IEmergencyStop { + /** + * @dev A multisig can stop this contract. Once stopped we will have to migrate. + * Once this function is called, the contract cannot be made live again. + */ + function emergencyStop() external; +} diff --git a/contracts/dao/governance/interfaces/IGovernor.sol b/contracts/dao/governance/interfaces/IGovernor.sol index a3a2e27..2a65ded 100644 --- a/contracts/dao/governance/interfaces/IGovernor.sol +++ b/contracts/dao/governance/interfaces/IGovernor.sol @@ -39,98 +39,71 @@ abstract contract IGovernor is IERC165 { event VoteCastWithParams(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 weight, string reason, bytes params); function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public virtual returns (uint256 proposalId); + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, + string calldata description + ) external virtual returns (uint256 proposalId); function cancelProposal( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, bytes32 descriptionHash - ) public virtual returns (uint256); + ) external virtual returns (uint256); function execute( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, bytes32 descriptionHash - ) public payable virtual returns (uint256 proposalId); + ) external payable virtual returns (uint256 proposalId); - function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance); + function castVote(uint256 proposalId, uint8 support) external virtual returns (uint256 balance); - function castVoteWithReason( - uint256 proposalId, - uint8 support, - string memory reason - ) public virtual returns (uint256 balance); + function castVoteWithReason(uint256 proposalId, uint8 support, string calldata reason) external virtual returns (uint256 balance); function castVoteWithReasonAndParams( uint256 proposalId, uint8 support, - string memory reason, - bytes memory params - ) public virtual returns (uint256 balance); + string calldata reason, + bytes calldata params + ) external virtual returns (uint256 balance); - function castVoteBySig( - uint256 proposalId, - uint8 support, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual returns (uint256 balance); + function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external virtual returns (uint256 balance); function castVoteWithReasonAndParamsBySig( uint256 proposalId, uint8 support, - string memory reason, - bytes memory params, + string calldata reason, + bytes calldata params, uint8 v, bytes32 r, bytes32 s - ) public virtual returns (uint256 balance); - - function getProposals(uint256 _numIndexes) - public - view - virtual - returns ( - string[] memory, - string[] memory, - string[] memory - ); - - function getDescription(uint256 _proposalId) public view virtual returns (string memory); + ) external virtual returns (uint256 balance); - function getProposalIds() public view virtual returns (uint256[] memory); + function getProposals(uint256 _numIndexes) external view virtual returns (string[] memory, string[] memory, string[] memory); - function name() public view virtual returns (string memory); + function getDescription(uint256 _proposalId) external view virtual returns (string memory); - function version() public view virtual returns (string memory); + function getProposalIds() external view virtual returns (uint256[] memory); - function state(uint256 proposalId) public view virtual returns (ProposalState); + function name() external view virtual returns (string memory); - function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256); + function version() external view virtual returns (string memory); - function proposalDeadline(uint256 proposalId) public view virtual returns (uint256); + function state(uint256 proposalId) external view virtual returns (ProposalState); - function votingDelay() public view virtual returns (uint256); + function proposalSnapshot(uint256 proposalId) external view virtual returns (uint256); - function votingPeriod() public view virtual returns (uint256); + function proposalDeadline(uint256 proposalId) external view virtual returns (uint256); - function quorum(uint256 blockNumber) public view virtual returns (uint256); + function getVotes(address account, uint256 blockNumber) external view virtual returns (uint256); - function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256); + function getVotesWithParams(address account, uint256 blockNumber, bytes calldata params) external view virtual returns (uint256); - function getVotesWithParams( - address account, - uint256 blockNumber, - bytes memory params - ) public view virtual returns (uint256); + function hasVoted(uint256 proposalId, address account) external view virtual returns (bool); - function hasVoted(uint256 proposalId, address account) public view virtual returns (bool); /** * @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to * be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded @@ -154,9 +127,15 @@ abstract contract IGovernor is IERC165 { */ function hashProposal( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata calldatas, bytes32 descriptionHash - ) public pure virtual returns (uint256); + ) external pure virtual returns (uint256); + + function votingDelay() public view virtual returns (uint256); + + function votingPeriod() public view virtual returns (uint256); + + function quorum(uint256 blockNumber) public view virtual returns (uint256); } diff --git a/contracts/dao/governance/interfaces/IRelay.sol b/contracts/dao/governance/interfaces/IRelay.sol new file mode 100644 index 0000000..919bd69 --- /dev/null +++ b/contracts/dao/governance/interfaces/IRelay.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 +pragma solidity 0.8.16; + +interface IRelay { + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * in a governance proposal to recover tokens that was sent to the governor contract by mistake. + * Note that if the executor is simply the governor itself, use of `relay` is redundant. + */ + function relayERC20(address target, bytes calldata data) external; + + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * in a governance proposal to recover Ether that was sent to the governor contract by mistake. + * Note that if the executor is simply the governor itself, use of `relay` is redundant. + */ + function relayNativeToken(address target, uint256 value, bytes calldata data) external payable; +} diff --git a/contracts/dao/governance/interfaces/ISupportingTokens.sol b/contracts/dao/governance/interfaces/ISupportingTokens.sol new file mode 100644 index 0000000..67c345f --- /dev/null +++ b/contracts/dao/governance/interfaces/ISupportingTokens.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 +pragma solidity 0.8.16; + +interface ISupportingTokens { + /** + * @dev Adds supporting tokens so that if there are tokens then it can be transferred + * Only Governance is able to access this function. + * It has to go through proposal and successful voting for execution. + */ + function addSupportingToken(address _token) external; + + /** + * @dev Removes supporting tokens + * Only Governance is able to access this function. + * It has to go through proposal and successful voting for execution. + */ + function removeSupportingToken(address _token) external; +} diff --git a/contracts/dao/governance/interfaces/ITimelockController.sol b/contracts/dao/governance/interfaces/ITimelockController.sol index b6c844a..95132a2 100644 --- a/contracts/dao/governance/interfaces/ITimelockController.sol +++ b/contracts/dao/governance/interfaces/ITimelockController.sol @@ -4,10 +4,62 @@ pragma solidity 0.8.16; interface ITimelockController { - function initialize( - uint256 minDelay, - address admin, - address[] calldata proposers, - address[] calldata executors + function initialize(uint256 minDelay, address admin, address[] calldata proposers, address[] calldata executors) external; + + function schedule(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay) external; + + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay ) external; + + function cancel(bytes32 id) external; + + function execute(address target, uint256 value, bytes calldata payload, bytes32 predecessor, bytes32 salt) external payable; + + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external payable; + + function updateDelay(uint256 newDelay) external; + + function grantRoleByAdmin(bytes32 role, address account) external; + + function revokeRoleByAdmin(bytes32 role, address account) external; + + function isOperation(bytes32 id) external view returns (bool registered); + + function isOperationPending(bytes32 id) external view returns (bool pending); + + function isOperationReady(bytes32 id) external view returns (bool ready); + + function isOperationDone(bytes32 id) external view returns (bool done); + + function getTimestamp(bytes32 id) external view returns (uint256 timestamp); + + function getMinDelay() external view returns (uint256 duration); + + function hashOperation( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt + ) external pure returns (bytes32 hash); + + function hashOperationBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external pure returns (bytes32 hash); } diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index fcb00ef..bb86268 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.16; import "./interfaces/IStakingStorage.sol"; -import "./library/StakingLibrary.sol"; contract StakingStorage { uint256 internal constant MAIN_STREAM = 0; diff --git a/contracts/dao/staking/helpers/IStakingGetterHelper.sol b/contracts/dao/staking/helpers/IStakingGetterHelper.sol index baf706f..4d752ff 100644 --- a/contracts/dao/staking/helpers/IStakingGetterHelper.sol +++ b/contracts/dao/staking/helpers/IStakingGetterHelper.sol @@ -1,5 +1,5 @@ -// Copyright SECURRENCY INC. // SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 pragma solidity 0.8.16; import "../StakingStructs.sol"; @@ -11,16 +11,7 @@ import "../../../common/security/IAdminPausable.sol"; interface IStakingGetterHelper { function getLockInfo(address account, uint256 lockId) external view returns (LockedBalance memory); - function getLock(address account, uint256 lockId) - external - view - returns ( - uint128, - uint128, - uint64, - address, - uint256 - ); + function getLock(address account, uint256 lockId) external view returns (uint128, uint128, uint64, address, uint256); function getUserTotalDeposit(address account) external view returns (uint256); @@ -31,5 +22,6 @@ interface IStakingGetterHelper { function getFeesForEarlyUnlock(uint256 lockId, address account) external view returns (uint256); function getLocksLength(address account) external view returns (uint256); - function getWeight() external view returns (Weight memory); + + function getWeight() external view returns (Weight memory); } diff --git a/contracts/dao/staking/helpers/IStakingHelper.sol b/contracts/dao/staking/helpers/IStakingHelper.sol index fe0e212..d93edf2 100644 --- a/contracts/dao/staking/helpers/IStakingHelper.sol +++ b/contracts/dao/staking/helpers/IStakingHelper.sol @@ -1,5 +1,5 @@ -// Copyright SECURRENCY INC. // SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 pragma solidity 0.8.16; import "../StakingStructs.sol"; diff --git a/contracts/dao/staking/helpers/StakingGettersHelper.sol b/contracts/dao/staking/helpers/StakingGettersHelper.sol index 5065930..52a4403 100644 --- a/contracts/dao/staking/helpers/StakingGettersHelper.sol +++ b/contracts/dao/staking/helpers/StakingGettersHelper.sol @@ -1,5 +1,5 @@ -// Copyright SECURRENCY INC. // SPDX-License-Identifier: AGPL 3.0 +// Copyright Fathom 2022 pragma solidity 0.8.16; import "./IStakingHelper.sol"; @@ -8,48 +8,34 @@ import "../interfaces/IStakingGetter.sol"; import "../StakingStructs.sol"; import "../../../common/access/AccessControl.sol"; +// solhint-disable not-rely-on-time contract StakingGettersHelper is IStakingGetterHelper, AccessControl { address private stakingContract; + + error LockOpenedError(); + error LockIdOutOfIndexError(); + error LockIdCantBeZeroError(); + constructor(address _stakingContract, address admin) { stakingContract = _stakingContract; _grantRole(DEFAULT_ADMIN_ROLE, admin); } - function getLockInfo(address account, uint256 lockId) public view override returns (LockedBalance memory) { - LockedBalance[] memory locks = _getAllLocks(account); - require(lockId <= locks.length, "out of index"); - require(lockId > 0, "lockId cant be 0"); - return locks[lockId - 1]; - } - - function getLocksLength(address account) public view override returns (uint256) { + function getLocksLength(address account) external view override returns (uint256) { LockedBalance[] memory locks = _getAllLocks(account); return locks.length; } - function getWeight() public view override returns (Weight memory) { + + function getWeight() external view override returns (Weight memory) { return _getWeight(); } - function getLock(address account, uint256 lockId) - public - view - override - returns ( - uint128, - uint128, - uint64, - address, - uint256 - ) - { - LockedBalance[] memory locks = _getAllLocks(account); - LockedBalance memory lock = locks[lockId - 1]; - require(lockId <= locks.length, "out of index"); - require(lockId > 0, "lockId cant be 0"); - return (lock.amountOfToken, lock.positionStreamShares, lock.end, lock.owner,lock.amountOfVoteToken); + function getLock(address account, uint256 lockId) external view override returns (uint128, uint128, uint64, address, uint256) { + LockedBalance memory lock = getLockInfo(account, lockId); + return (lock.amountOfToken, lock.positionStreamShares, lock.end, lock.owner, lock.amountOfVoteToken); } - function getUserTotalDeposit(address account) public view override returns (uint256) { + function getUserTotalDeposit(address account) external view override returns (uint256) { LockedBalance[] memory locks = _getAllLocks(account); if (locks.length == 0) { return 0; @@ -61,7 +47,7 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { return totalDeposit; } - function getStreamClaimableAmount(uint256 streamId, address account) public view override returns (uint256) { + function getStreamClaimableAmount(uint256 streamId, address account) external view override returns (uint256) { LockedBalance[] memory locks = _getAllLocks(account); if (locks.length == 0) { return 0; @@ -73,7 +59,7 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { return totalRewards; } - function getUserTotalVotes(address account) public view override returns (uint256) { + function getUserTotalVotes(address account) external view override returns (uint256) { LockedBalance[] memory locks = _getAllLocks(account); if (locks.length == 0) { return 0; @@ -85,12 +71,11 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { return totalVotes; } - function getFeesForEarlyUnlock(uint256 lockId, address account) public view override returns (uint256) { - LockedBalance[] memory locks = _getAllLocks(account); - require(lockId <= locks.length, "out of index"); - LockedBalance memory lock = locks[lockId - 1]; - require(lockId > 0, "lockId cant be 0"); - require(lock.end > block.timestamp, "lock opened, no penalty"); + function getFeesForEarlyUnlock(uint256 lockId, address account) external view override returns (uint256) { + LockedBalance memory lock = getLockInfo(account, lockId); + if (lock.end <= block.timestamp) { + revert LockOpenedError(); + } uint256 amount = lock.amountOfToken; uint256 lockEnd = lock.end; @@ -99,11 +84,21 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { return penalty; } + function getLockInfo(address account, uint256 lockId) public view override returns (LockedBalance memory) { + LockedBalance[] memory locks = _getAllLocks(account); + if (lockId > locks.length) { + revert LockIdOutOfIndexError(); + } + if (lockId == 0) { + revert LockIdCantBeZeroError(); + } + return locks[lockId - 1]; + } - - function _getAllLocks(address account) internal view returns(LockedBalance[] memory) { + function _getAllLocks(address account) internal view returns (LockedBalance[] memory) { return IStakingHelper(stakingContract).getAllLocks(account); } + function _weightedPenalty(uint256 lockEnd, uint256 timestamp) internal view returns (uint256) { Weight memory weight = _getWeight(); uint256 maxLockPeriod = IStakingHelper(stakingContract).maxLockPeriod(); @@ -118,7 +113,8 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { (weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) / maxLockPeriod); } - function _getWeight() internal view returns (Weight memory) { - return IStakingStorage(stakingContract).weight(); + + function _getWeight() internal view returns (Weight memory) { + return IStakingStorage(stakingContract).weight(); } } diff --git a/contracts/dao/staking/interfaces/IRewardsHandler.sol b/contracts/dao/staking/interfaces/IRewardsHandler.sol index d968575..80c34bc 100644 --- a/contracts/dao/staking/interfaces/IRewardsHandler.sol +++ b/contracts/dao/staking/interfaces/IRewardsHandler.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL 3.0 // Copyright Fathom 2022 -pragma solidity ^0.8.13; +pragma solidity 0.8.16; import "../StakingStructs.sol"; @@ -11,10 +11,10 @@ interface IRewardsHandler { address rewardToken, uint256 maxDepositAmount, uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, uint256 tau ) external view; - function getRewardsAmount(Schedule memory schedule, uint256 lastUpdate) external view returns (uint256); + function getRewardsAmount(Schedule calldata schedule, uint256 lastUpdate) external view returns (uint256); } diff --git a/contracts/dao/staking/interfaces/IStakingGetter.sol b/contracts/dao/staking/interfaces/IStakingGetter.sol index 4be004b..4dba3c5 100644 --- a/contracts/dao/staking/interfaces/IStakingGetter.sol +++ b/contracts/dao/staking/interfaces/IStakingGetter.sol @@ -6,24 +6,15 @@ pragma solidity 0.8.16; import "../StakingStructs.sol"; interface IStakingGetter { - function getAllLocks(address account) external view returns (LockedBalance[] memory); + function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256); - function getStreamClaimableAmountPerLock( - uint256 streamId, - address account, - uint256 lockId - ) external view returns (uint256); + + function getStreamClaimableAmountPerLock(uint256 streamId, address account, uint256 lockId) external view returns (uint256); + function getStreamSchedule(uint256 streamId) external view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards); + function getStream( uint256 streamId - ) - external - view - returns ( - uint256 rewardDepositAmount, - uint256 rewardClaimedAmount, - uint256 rps, - StreamStatus status - ); + ) external view returns (uint256 rewardDepositAmount, uint256 rewardClaimedAmount, uint256 rps, StreamStatus status); } diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index 8283b2c..782fbb9 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -7,33 +7,27 @@ import "../StakingStructs.sol"; import "./IStakingGetter.sol"; interface IStakingHandler { - function initializeStaking( address _admin, address _vault, address _mainToken, address _voteToken, Weight calldata _weight, - VoteCoefficient memory voteCoef, + VoteCoefficient calldata voteCoef, uint256 _maxLocks, address _rewardsContract, uint256 _minLockPeriod ) external; - function initializeMainStream( - address _owner, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, - uint256 tau - ) external; + function initializeMainStream(address _owner, uint256[] calldata scheduleTimes, uint256[] calldata scheduleRewards, uint256 tau) external; function proposeStream( address streamOwner, address rewardToken, uint256 maxDepositAmount, uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, uint256 tau ) external; // only STREAM_MANAGER_ROLE @@ -60,12 +54,16 @@ interface IStakingHandler { function withdrawAllStreams() external; function withdrawPenalty(address penaltyReceiver) external; + function updateVault(address _vault) external; function emergencyUnlockAndWithdraw() external; function createLocksForCouncils(CreateLockParams[] calldata lockParams) external; + function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) external; + function setMinimumLockPeriod(uint256 _minLockPeriod) external; + function setMaxLockPositions(uint256 newMaxLockPositions) external; } diff --git a/contracts/dao/staking/interfaces/IStakingStorage.sol b/contracts/dao/staking/interfaces/IStakingStorage.sol index 5553736..bcf4d2b 100644 --- a/contracts/dao/staking/interfaces/IStakingStorage.sol +++ b/contracts/dao/staking/interfaces/IStakingStorage.sol @@ -17,6 +17,6 @@ interface IStakingStorage { function totalPenaltyBalance() external view returns (uint256); function streamTotalUserPendings(uint256 streamId) external view returns (uint256); - function weight() external view returns (Weight memory); + function weight() external view returns (Weight memory); } diff --git a/contracts/dao/staking/library/RewardsLibrary.sol b/contracts/dao/staking/library/RewardsLibrary.sol deleted file mode 100644 index c5c34f4..0000000 --- a/contracts/dao/staking/library/RewardsLibrary.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: AGPL 3.0 -// Original Copyright Aurora -// Copyright Fathom 2022 -pragma solidity 0.8.16; - -import "../StakingStructs.sol"; -import "../../../common/math/FullMath.sol"; - -library RewardsLibrary { - // solhint-disable not-rely-on-time - function _validateStreamParameters( - address streamOwner, - address rewardToken, - uint256 maxDepositAmount, - uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, - uint256 tau - ) public view { - require(streamOwner != address(0), "bad owner"); - require(rewardToken != address(0), "bad reward token"); - require(minDepositAmount > 0, "No Min Deposit"); - require(minDepositAmount <= maxDepositAmount, "bad Min Deposit"); - require(maxDepositAmount == scheduleRewards[0], "Invalid Max Deposit"); - // scheduleTimes[0] == proposal expiration time - require(scheduleTimes[0] > block.timestamp, "bad expiration"); - require(scheduleTimes.length == scheduleRewards.length, "bad Schedules"); - require(scheduleTimes.length >= 2, "Schedules short"); - require(tau != 0, "bad Tau"); - for (uint256 i = 1; i < scheduleTimes.length; i++) { - require(scheduleTimes[i] > scheduleTimes[i - 1], "bad times"); - require(scheduleRewards[i] <= scheduleRewards[i - 1], "bad Rewards"); - } - require(scheduleRewards[scheduleRewards.length - 1] == 0, "bad End Rewards"); - } - - function _getRewardsAmount(Stream memory stream, uint256 lastUpdate) public view returns (uint256) { - require(lastUpdate <= block.timestamp, "bad last Update"); - if (lastUpdate == block.timestamp) return 0; // No more rewards since last update - uint256 streamStart = stream.schedule.time[0]; - if (block.timestamp <= streamStart) return 0; // Stream didn't start - uint256 streamEnd = stream.schedule.time[stream.schedule.time.length - 1]; - if (lastUpdate >= streamEnd) return 0; // Stream schedule ended, all rewards released - uint256 start; - uint256 end; - if (lastUpdate > streamStart) { - start = lastUpdate; - } else { - // Release rewards from stream start. - start = streamStart; - } - if (block.timestamp < streamEnd) { - end = block.timestamp; - } else { - // The stream already finished between the last update and now. - end = streamEnd; - } - return _getRewardsSchedule(stream, start, end); - } - - function _getRewardsSchedule( - Stream memory stream, - uint256 start, - uint256 end - ) internal pure returns (uint256) { - Schedule memory schedule = stream.schedule; - uint256 startIndex; - uint256 endIndex; - (startIndex, endIndex) = _getStartEndScheduleIndex(stream, start, end); - uint256 rewardScheduledAmount = 0; - uint256 reward = 0; - if (startIndex == endIndex) { - // start and end are within the same schedule period - reward = schedule.reward[startIndex] - schedule.reward[startIndex + 1]; - rewardScheduledAmount = FullMath.mulDiv(reward, (end - start), (schedule.time[startIndex + 1] - schedule.time[startIndex])); - } else { - // start and end are not within the same schedule period - // Reward during the startIndex period - // Here reward = starting from the actual start time, calculated for the first schedule period - // that the rewards start. - reward = schedule.reward[startIndex] - schedule.reward[startIndex + 1]; - rewardScheduledAmount = FullMath.mulDiv(reward,(schedule.time[startIndex + 1] - start),(schedule.time[startIndex + 1] - schedule.time[startIndex])); // Here reward = from end of start schedule till beginning of end schedule - // Reward during the period from startIndex + 1 to endIndex - rewardScheduledAmount += schedule.reward[startIndex + 1] - schedule.reward[endIndex]; - // Reward at the end schedule where schedule.time[endIndex] - reward = schedule.reward[endIndex] - schedule.reward[endIndex + 1]; - rewardScheduledAmount += FullMath.mulDiv( - reward, - (end - schedule.time[endIndex]), - (schedule.time[endIndex + 1] - schedule.time[endIndex]) - ); - } - return rewardScheduledAmount; - } - - function _getStartEndScheduleIndex( - Stream memory stream, - uint256 start, - uint256 end - ) internal pure returns (uint256 startIndex, uint256 endIndex) { - Schedule memory schedule = stream.schedule; - uint256 scheduleTimeLength = schedule.time.length; - require(scheduleTimeLength > 0, "bad schedules"); - require(end > start, "bad query period"); - require(start >= schedule.time[0], "query before start"); - require(end <= schedule.time[scheduleTimeLength - 1], "query after end"); - for (uint256 i = 1; i < scheduleTimeLength; i++) { - if (start < schedule.time[i]) { - startIndex = i - 1; - break; - } - } - if (end == schedule.time[scheduleTimeLength - 1]) { - endIndex = scheduleTimeLength - 2; - } else { - for (uint256 i = startIndex + 1; i < scheduleTimeLength; i++) { - if (end < schedule.time[i]) { - // Users most often claim rewards within the same index which can last several months. - endIndex = i - 1; - break; - } - } - } - require(startIndex <= endIndex, "invalid index"); - } -} diff --git a/contracts/dao/staking/library/StakingLibrary.sol b/contracts/dao/staking/library/StakingLibrary.sol deleted file mode 100644 index ac6554e..0000000 --- a/contracts/dao/staking/library/StakingLibrary.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL 3.0 -// Original Copyright Aurora -// Copyright Fathom 2022 -pragma solidity 0.8.16; - -import "../StakingStructs.sol"; - -library StakingLibrary { - function _caclulateAutoCompoundingShares( - uint256 amount, - uint256 totalShares, - uint256 totalAmountOfStakedToken - ) internal pure returns (uint256) { - uint256 _amountOfShares = 0; - if (totalShares == 0) { - _amountOfShares = amount; - } else { - uint256 numerator = amount * totalShares; - _amountOfShares = numerator / totalAmountOfStakedToken; - if (_amountOfShares * totalAmountOfStakedToken < numerator) { - _amountOfShares += 1; - } - } - - return _amountOfShares; - } - - function _getWeightedPenalty( - uint256 lockEnd, - uint256 timestamp, - Weight memory weight, - uint256 maxLock - ) internal pure returns (uint256) { - uint256 slopeStart = lockEnd; - if (timestamp >= slopeStart) return 0; - uint256 remainingTime = slopeStart - timestamp; - //why weight multiplier: Because if a person remaining time is less than 12 hours, the calculation - //would only give minWeightPenalty, because 2900 * 12hours/4days = 0 - return (weight.penaltyWeightMultiplier * - weight.minWeightPenalty + - (weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) / - maxLock); - } -} diff --git a/contracts/dao/staking/packages/RewardsCalculator.sol b/contracts/dao/staking/packages/RewardsCalculator.sol index 473ab4a..63de490 100644 --- a/contracts/dao/staking/packages/RewardsCalculator.sol +++ b/contracts/dao/staking/packages/RewardsCalculator.sol @@ -6,36 +6,84 @@ import "../StakingStructs.sol"; import "../interfaces/IRewardsHandler.sol"; import "../../../common/math/FullMath.sol"; +// solhint-disable not-rely-on-time contract RewardsCalculator is IRewardsHandler { - // solhint-disable not-rely-on-time + error BadOwnerError(); + error BadRewardTokenError(); + error NoMinDepositError(); + error BadMinDepositError(); + error InvalidMaxDepositError(); + error BadExpirationError(); + error BadSchedulesLengthError(); + error SchedulesShortError(); + error BadTauError(); + error BadTimesError(); + error BadRewardsError(); + error BadEndRewardsError(); + error BadLastUpdateError(); + error BadSchedulesError(); + error BadQueryPeriodError(); + error QueryBeforeStartError(); + error QueryAfterEndError(); + error InvalidIndexError(); + + // solhint-disable code-complexity function validateStreamParameters( address streamOwner, address rewardToken, uint256 maxDepositAmount, uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, uint256 tau - ) public view override { - require(streamOwner != address(0), "bad owner"); - require(rewardToken != address(0), "bad reward token"); - require(minDepositAmount > 0, "No Min Deposit"); - require(minDepositAmount <= maxDepositAmount, "bad Min Deposit"); - require(maxDepositAmount == scheduleRewards[0], "Invalid Max Deposit"); + ) external view override { + if (streamOwner == address(0)) { + revert BadOwnerError(); + } + if (rewardToken == address(0)) { + revert BadRewardTokenError(); + } + if (minDepositAmount == 0) { + revert NoMinDepositError(); + } + if (minDepositAmount > maxDepositAmount) { + revert BadMinDepositError(); + } + if (maxDepositAmount != scheduleRewards[0]) { + revert InvalidMaxDepositError(); + } // scheduleTimes[0] == proposal expiration time - require(scheduleTimes[0] > block.timestamp, "bad expiration"); - require(scheduleTimes.length == scheduleRewards.length, "bad Schedules"); - require(scheduleTimes.length >= 2, "Schedules short"); - require(tau != 0, "bad Tau"); + if (scheduleTimes[0] <= block.timestamp) { + revert BadExpirationError(); + } + if (scheduleTimes.length != scheduleRewards.length) { + revert BadSchedulesLengthError(); + } + if (scheduleTimes.length < 2) { + revert SchedulesShortError(); + } + if (tau == 0) { + revert BadTauError(); + } for (uint256 i = 1; i < scheduleTimes.length; i++) { - require(scheduleTimes[i] > scheduleTimes[i - 1], "bad times"); - require(scheduleRewards[i] <= scheduleRewards[i - 1], "bad Rewards"); + if (scheduleTimes[i] <= scheduleTimes[i - 1]) { + revert BadTimesError(); + } + if (scheduleRewards[i] > scheduleRewards[i - 1]) { + revert BadRewardsError(); + } + } + if (scheduleRewards[scheduleRewards.length - 1] > 0) { + revert BadEndRewardsError(); } - require(scheduleRewards[scheduleRewards.length - 1] == 0, "bad End Rewards"); } - function getRewardsAmount(Schedule memory schedule, uint256 lastUpdate) public view override returns (uint256) { - require(lastUpdate <= block.timestamp, "bad last Update"); + // solhint-enable code-complexity + + function getRewardsAmount(Schedule calldata schedule, uint256 lastUpdate) external view override returns (uint256) { + if (lastUpdate > block.timestamp) { + revert BadLastUpdateError(); + } if (lastUpdate == block.timestamp) return 0; // No more rewards since last update uint256 streamStart = schedule.time[0]; if (block.timestamp <= streamStart) return 0; // Stream didn't start @@ -58,11 +106,7 @@ contract RewardsCalculator is IRewardsHandler { return _getRewardsSchedule(schedule, start, end); } - function _getRewardsSchedule( - Schedule memory schedule, - uint256 start, - uint256 end - ) internal pure returns (uint256) { + function _getRewardsSchedule(Schedule memory schedule, uint256 start, uint256 end) internal pure returns (uint256) { uint256 startIndex; uint256 endIndex; (startIndex, endIndex) = _getStartEndScheduleIndex(schedule, start, end); @@ -78,7 +122,11 @@ contract RewardsCalculator is IRewardsHandler { // Here reward = starting from the actual start time, calculated for the first schedule period // that the rewards start. reward = schedule.reward[startIndex] - schedule.reward[startIndex + 1]; - rewardScheduledAmount = FullMath.mulDiv(reward,(schedule.time[startIndex + 1] - start),(schedule.time[startIndex + 1] - schedule.time[startIndex])); + rewardScheduledAmount = FullMath.mulDiv( + reward, + (schedule.time[startIndex + 1] - start), + (schedule.time[startIndex + 1] - schedule.time[startIndex]) + ); // Here reward = from end of start schedule till beginning of end schedule // Reward during the period from startIndex + 1 to endIndex rewardScheduledAmount += schedule.reward[startIndex + 1] - schedule.reward[endIndex]; @@ -93,16 +141,25 @@ contract RewardsCalculator is IRewardsHandler { return rewardScheduledAmount; } + // solhint-disable code-complexity function _getStartEndScheduleIndex( Schedule memory schedule, uint256 start, uint256 end ) internal pure returns (uint256 startIndex, uint256 endIndex) { uint256 scheduleTimeLength = schedule.time.length; - require(scheduleTimeLength >= 2, "bad schedules"); - require(end > start, "bad query period"); - require(start >= schedule.time[0], "query before start"); - require(end <= schedule.time[scheduleTimeLength - 1], "query after end"); + if (scheduleTimeLength < 2) { + revert BadSchedulesError(); + } + if (end <= start) { + revert BadQueryPeriodError(); + } + if (start < schedule.time[0]) { + revert QueryBeforeStartError(); + } + if (end > schedule.time[scheduleTimeLength - 1]) { + revert QueryAfterEndError(); + } for (uint256 i = 1; i < scheduleTimeLength; i++) { if (start < schedule.time[i]) { startIndex = i - 1; @@ -120,6 +177,9 @@ contract RewardsCalculator is IRewardsHandler { } } } - require(startIndex <= endIndex, "invalid index"); + if (startIndex > endIndex) { + revert InvalidIndexError(); + } } + // solhint-enable code-complexity } diff --git a/contracts/dao/staking/packages/RewardsInternals.sol b/contracts/dao/staking/packages/RewardsInternals.sol index 22decc9..4c45c5d 100644 --- a/contracts/dao/staking/packages/RewardsInternals.sol +++ b/contracts/dao/staking/packages/RewardsInternals.sol @@ -6,10 +6,16 @@ pragma solidity 0.8.16; import "../StakingStorage.sol"; import "../interfaces/IStakingEvents.sol"; import "../interfaces/IRewardsHandler.sol"; -//import "../../../common/math/FullMath.sol"; contract RewardsInternals is StakingStorage, IStakingEvents { // solhint-disable not-rely-on-time + + error InactiveStreamError(); + error NoStakeError(); + error InsufficientRewardsError(); + error NoLockError(); + error NoSharesError(); + function _updateStreamsRewardsSchedules(uint256 streamId, uint256 rewardTokenAmount) internal { uint256 streamScheduleRewardLength = streams[streamId].schedule.reward.length; for (uint256 i; i < streamScheduleRewardLength; i++) { @@ -17,24 +23,29 @@ contract RewardsInternals is StakingStorage, IStakingEvents { } } - function _moveRewardsToPending( - address account, - uint256 streamId, - uint256 lockId - ) internal { + function _moveRewardsToPending(address account, uint256 streamId, uint256 lockId) internal { + if (streams[streamId].status != StreamStatus.ACTIVE) { + revert InactiveStreamError(); + } LockedBalance storage lock = locks[account][lockId - 1]; - require(streams[streamId].status == StreamStatus.ACTIVE, "inactive"); + if (lock.amountOfToken == 0) { + revert NoStakeError(); + } + User storage userAccount = users[account]; - require(lock.amountOfToken != 0, "No Stake"); + uint256 reward = ((streams[streamId].rps - userAccount.rpsDuringLastClaimForLock[lockId][streamId]) * lock.positionStreamShares) / RPS_MULTIPLIER; if (reward == 0) return; // All rewards claimed or stream schedule didn't start + if (streams[streamId].rewardClaimedAmount + reward > streams[streamId].rewardDepositAmount) { + revert InsufficientRewardsError(); + } + userAccount.pendings[streamId] += reward; streamTotalUserPendings[streamId] += reward; userAccount.rpsDuringLastClaimForLock[lockId][streamId] = streams[streamId].rps; userAccount.releaseTime[streamId] = block.timestamp + streams[streamId].tau; - // If the stream is blacklisted, remaining unclaimed rewards will be transfered out. - require(streams[streamId].rewardClaimedAmount + reward <= streams[streamId].rewardDepositAmount, "insufficient rewards"); + // If the stream is blocklisted, remaining unclaimed rewards will be transfered out. streams[streamId].rewardClaimedAmount += reward; emit Pending(streamId, account, userAccount.pendings[streamId]); } @@ -47,10 +58,14 @@ contract RewardsInternals is StakingStorage, IStakingEvents { } function _moveAllLockPositionRewardsToPending(address account, uint256 streamId) internal { - require(streams[streamId].status == StreamStatus.ACTIVE, "inactive"); + if (streams[streamId].status != StreamStatus.ACTIVE) { + revert InactiveStreamError(); + } LockedBalance[] storage locksOfAccount = locks[account]; uint256 locksLength = locksOfAccount.length; - require(locksLength > 0, "no lock"); + if (locksLength == 0) { + revert NoLockError(); + } for (uint256 i = 1; i <= locksLength; i++) { _moveRewardsToPending(account, streamId, i); } @@ -93,7 +108,9 @@ contract RewardsInternals is StakingStorage, IStakingEvents { } function _getLatestRewardsPerShare(uint256 streamId) internal view returns (uint256) { - require(totalStreamShares != 0, "No Shares"); + if (totalStreamShares == 0) { + revert NoSharesError(); + } return streams[streamId].rps + (_getRewardsAmount(streamId, touchedAt) * RPS_MULTIPLIER) / totalStreamShares; } } diff --git a/contracts/dao/staking/packages/StakingGetters.sol b/contracts/dao/staking/packages/StakingGetters.sol index 1a648e7..1293549 100644 --- a/contracts/dao/staking/packages/StakingGetters.sol +++ b/contracts/dao/staking/packages/StakingGetters.sol @@ -9,16 +9,20 @@ import "../interfaces/IStakingGetter.sol"; import "./StakingInternals.sol"; contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { - function getUsersPendingRewards(address account, uint256 streamId) external override view returns (uint256) { + error StreamInactiveError(); + error BadIndexError(); + + function getUsersPendingRewards(address account, uint256 streamId) external view override returns (uint256) { return users[account].pendings[streamId]; } - function getStreamClaimableAmountPerLock( - uint256 streamId, - address account, - uint256 lockId - ) external view override returns (uint256) { - require(streams[streamId].status == StreamStatus.ACTIVE, "stream inactive"); - require(lockId <= locks[account].length, "bad index"); + + function getStreamClaimableAmountPerLock(uint256 streamId, address account, uint256 lockId) external view override returns (uint256) { + if (streams[streamId].status != StreamStatus.ACTIVE) { + revert StreamInactiveError(); + } + if (lockId > locks[account].length) { + revert BadIndexError(); + } uint256 latestRps = _getLatestRewardsPerShare(streamId); User storage userAccount = users[account]; LockedBalance storage lock = locks[account][lockId - 1]; @@ -27,34 +31,18 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { return ((latestRps - userRpsPerLock) * userSharesOfLock) / RPS_MULTIPLIER; } - - function getAllLocks(address account) external override view returns (LockedBalance[] memory) { + function getAllLocks(address account) external view override returns (LockedBalance[] memory) { return locks[account]; } - function getStreamSchedule(uint256 streamId) external override view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards) { + + function getStreamSchedule(uint256 streamId) external view override returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards) { return (streams[streamId].schedule.time, streams[streamId].schedule.reward); } function getStream( uint256 streamId - ) - external - override - view - returns ( - uint256 rewardDepositAmount, - uint256 rewardClaimedAmount, - uint256 rps, - StreamStatus status - ) - { + ) external view override returns (uint256 rewardDepositAmount, uint256 rewardClaimedAmount, uint256 rps, StreamStatus status) { Stream storage stream = streams[streamId]; - return ( - stream.rewardDepositAmount, - stream.rewardClaimedAmount, - stream.rps, - stream.status - ); + return (stream.rewardDepositAmount, stream.rewardClaimedAmount, stream.rps, stream.status); } - } diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 0b52f2a..d323027 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL 3.0 // Original Copyright Aurora // Copyright Fathom 2022 - pragma solidity 0.8.16; import "./StakingInternals.sol"; @@ -10,12 +9,13 @@ import "../interfaces/IStakingHandler.sol"; import "../vault/interfaces/IVault.sol"; import "../../../common/security/AdminPausable.sol"; import "../../../common/SafeERC20Staking.sol"; + // solhint-disable not-rely-on-time contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, AdminPausable { using SafeERC20Staking for IERC20; bytes32 public constant STREAM_MANAGER_ROLE = keccak256("STREAM_MANAGER_ROLE"); bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); - + error NotPaused(); error VaultNotSupported(address _vault); error VaultNotMigrated(address _vault); @@ -27,9 +27,30 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error StreamIdZero(); error BadMaxLockPositions(); error StreamNotWithdrawn(); + error NotProposed(); + error ProposalExpired(); + error NotOwner(); + error RewardsTooHigh(); + error RewardsTooLow(); + error LockNotClosed(); + error EarlyWithdrawalInfeasible(); + error LockAlreadyOpen(); + error UnsupportedToken(); + error BadStart(); + error NoActiveStream(); + error LockNotExpired(); + error NoPendings(); + error NotReleased(); + error StreamInactive(); + error MinLockPeriodNotMet(); + error MaxLockPositionsReached(); + error ZeroAmount(); + error NotLockOwner(); + constructor() { _disableInitializers(); } + /** * @dev initialize the contract and deploys the first stream of rewards * @dev initializable only once due to stakingInitialised flag @@ -54,7 +75,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A ) external override initializer { rewardsCalculator = _rewardsContract; _initializeStaking(_mainToken, _voteToken, _weight, _vault, _maxLocks, voteCoef.voteShareCoef, voteCoef.voteLockCoef); - require(IVault(vault).isSupportedToken(_mainToken), "!token"); + if (!IVault(vault).isSupportedToken(_mainToken)) { + revert UnsupportedToken(); + } pausableInit(1, _admin); _grantRole(STREAM_MANAGER_ROLE, _admin); _grantRole(TREASURY_ROLE, _admin); @@ -68,14 +91,14 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A */ function initializeMainStream( address _owner, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, uint256 tau - ) external override onlyRole(DEFAULT_ADMIN_ROLE){ - if(mainStreamInitialized == true){ + ) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (mainStreamInitialized == true) { revert AlreadyInitialized(); } - IERC20(mainToken).safeTransferFrom(msg.sender,address(this),scheduleRewards[0]); + IERC20(mainToken).safeTransferFrom(msg.sender, address(this), scheduleRewards[0]); _validateStreamParameters(_owner, mainToken, scheduleRewards[MAIN_STREAM], scheduleRewards[MAIN_STREAM], scheduleTimes, scheduleRewards, tau); uint256 streamId = 0; Schedule memory schedule = Schedule(scheduleTimes, scheduleRewards); @@ -95,15 +118,15 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A }) ); _adminPause(0); - mainStreamInitialized =true; - _transfer(scheduleRewards[0],mainToken); + mainStreamInitialized = true; emit StreamProposed(streamId, _owner, mainToken, scheduleRewards[MAIN_STREAM]); emit StreamCreated(streamId, _owner, mainToken, tau); + _transfer(scheduleRewards[0], mainToken); } /** - * @dev An admin of the staking contract can whitelist (propose) a stream. - * Whitelisting of the stream provides the option for the stream + * @dev An admin of the staking contract can allowlist (propose) a stream. + * Allowlisting of the stream provides the option for the stream * owner (presumably the issuing party of a specific token) to * deposit some ERC-20 tokens on the staking contract and potentially * get in return some main tokens immediately. @@ -123,12 +146,14 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A address rewardToken, uint256 maxDepositAmount, uint256 minDepositAmount, - uint256[] memory scheduleTimes, - uint256[] memory scheduleRewards, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, uint256 tau - ) public override onlyRole(STREAM_MANAGER_ROLE) { + ) external override onlyRole(STREAM_MANAGER_ROLE) { _validateStreamParameters(streamOwner, rewardToken, maxDepositAmount, minDepositAmount, scheduleTimes, scheduleRewards, tau); - require(IVault(vault).isSupportedToken(rewardToken), "!Token"); + if (!IVault(vault).isSupportedToken(rewardToken)) { + revert UnsupportedToken(); + } Schedule memory schedule = Schedule(scheduleTimes, scheduleRewards); uint256 streamId = streams.length; streams.push( @@ -148,19 +173,16 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A ); emit StreamProposed(streamId, streamOwner, rewardToken, maxDepositAmount); } + /** * @dev This function creates a stream and makes it live. Only the Stream Owner is able to call this function. * Stream Owner is set while proposing a stream */ - function createStream(uint256 streamId, uint256 rewardTokenAmount) public override pausable(1){ + function createStream(uint256 streamId, uint256 rewardTokenAmount) external override pausable(1) { Stream storage stream = streams[streamId]; - require(stream.status == StreamStatus.PROPOSED, "nt proposed"); - require(stream.schedule.time[0] >= block.timestamp, "prop expire"); - require(stream.owner == msg.sender, "bad owner"); + _verifyStream(stream, rewardTokenAmount); - require(rewardTokenAmount <= stream.maxDepositAmount, "rwrds high"); - require(rewardTokenAmount >= stream.minDepositAmount, "rwrds low"); - IERC20(stream.rewardToken).safeTransferFrom(msg.sender,address(this), rewardTokenAmount); + IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); stream.status = StreamStatus.ACTIVE; @@ -168,18 +190,22 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if (rewardTokenAmount < stream.maxDepositAmount) { _updateStreamsRewardsSchedules(streamId, rewardTokenAmount); } - require(stream.schedule.reward[0] == stream.rewardDepositAmount, "bad start"); + if (stream.schedule.reward[0] != stream.rewardDepositAmount) { + revert BadStart(); + } - emit StreamCreated(streamId, stream.owner, stream.rewardToken,stream.tau); - _transfer(rewardTokenAmount,stream.rewardToken); + emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); + _transfer(rewardTokenAmount, stream.rewardToken); } /** * @dev Proposed stream can be cancelled by Stream Manager, which at the time of deployment is Multisig */ - function cancelStreamProposal(uint256 streamId) public override onlyRole(STREAM_MANAGER_ROLE) { + function cancelStreamProposal(uint256 streamId) external override onlyRole(STREAM_MANAGER_ROLE) { Stream storage stream = streams[streamId]; - require(stream.status == StreamStatus.PROPOSED, "nt proposed"); + if (stream.status != StreamStatus.PROPOSED) { + revert NotProposed(); + } stream.status = StreamStatus.INACTIVE; emit StreamProposalCancelled(streamId, stream.owner, stream.rewardToken); @@ -189,15 +215,17 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev A stream can be removed after all the rewards pending have been withdrawn. * Stream can be removed by the Stream Manager which is Multisig as time of deployment. */ - function removeStream(uint256 streamId, address streamFundReceiver) public override onlyRole(STREAM_MANAGER_ROLE) { - if(streamId == 0){ + function removeStream(uint256 streamId, address streamFundReceiver) external override onlyRole(STREAM_MANAGER_ROLE) { + if (streamId == 0) { revert StreamIdZero(); } - if(streamTotalUserPendings[streamId]!=0){ + if (streamTotalUserPendings[streamId] != 0) { revert StreamNotWithdrawn(); } Stream storage stream = streams[streamId]; - require(stream.status == StreamStatus.ACTIVE, "No Stream"); + if (stream.status != StreamStatus.ACTIVE) { + revert NoActiveStream(); + } stream.status = StreamStatus.INACTIVE; uint256 releaseRewardAmount = stream.rewardDepositAmount - stream.rewardClaimedAmount; uint256 rewardTreasury = IERC20(stream.rewardToken).balanceOf(vault); @@ -214,8 +242,8 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Creating locks for council can be done by Admin only which is Multisig. * Multisig can create locks for councils */ - function createLocksForCouncils(CreateLockParams[] calldata lockParams) public override onlyRole(DEFAULT_ADMIN_ROLE) { - if(councilsInitialized == true){ + function createLocksForCouncils(CreateLockParams[] calldata lockParams) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (councilsInitialized == true) { revert AlreadyInitialized(); } councilsInitialized = true; @@ -225,51 +253,58 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A _createLock(lockParams[i].amount, lockParams[i].lockPeriod, account); } } - - function createLock(uint256 amount, uint256 lockPeriod) public override pausable(1) { + + function createLock(uint256 amount, uint256 lockPeriod) external override pausable(1) { _createLock(amount, lockPeriod, msg.sender); } - function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) public override pausable(1){ + function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) external override pausable(1) { prohibitedEarlyWithdraw[msg.sender][locks[msg.sender].length + 1] = true; _createLock(amount, lockPeriod, msg.sender); } - function unlock(uint256 lockId) public override pausable(1) { + function unlock(uint256 lockId) external override pausable(1) { _verifyUnlock(lockId); LockedBalance storage lock = locks[msg.sender][lockId - 1]; - require(lock.end <= block.timestamp, "lock close"); + if (lock.end > block.timestamp) { + revert LockNotClosed(); + } _updateStreamRPS(); uint256 stakeValue = lock.amountOfToken; prohibitedEarlyWithdraw[msg.sender][lockId] = false; _unlock(stakeValue, stakeValue, lockId, msg.sender); - } - function unlockPartially(uint256 lockId, uint256 amount) public override pausable(1) { + function unlockPartially(uint256 lockId, uint256 amount) external override pausable(1) { _verifyUnlock(lockId); LockedBalance storage lock = locks[msg.sender][lockId - 1]; - require(lock.end <= block.timestamp, "lock close"); + if (lock.end > block.timestamp) { + revert LockNotExpired(); + } _updateStreamRPS(); uint256 stakeValue = lock.amountOfToken; prohibitedEarlyWithdraw[msg.sender][lockId] = false; _unlock(stakeValue, amount, lockId, msg.sender); } - function earlyUnlock(uint256 lockId) public override pausable(1) { + function earlyUnlock(uint256 lockId) external override pausable(1) { _verifyUnlock(lockId); - require(prohibitedEarlyWithdraw[msg.sender][lockId] == false, "early infeasible"); + if (prohibitedEarlyWithdraw[msg.sender][lockId]) { + revert EarlyWithdrawalInfeasible(); + } LockedBalance storage lock = locks[msg.sender][lockId - 1]; - require(lock.end > block.timestamp, "lock open"); + if (lock.end <= block.timestamp) { + revert LockAlreadyOpen(); + } _updateStreamRPS(); _earlyUnlock(lockId, msg.sender); } - function claimAllStreamRewardsForLock(uint256 lockId) public override pausable(1) { - if(lockId > locks[msg.sender].length){ + function claimAllStreamRewardsForLock(uint256 lockId) external override pausable(1) { + if (lockId > locks[msg.sender].length) { revert MaxLockIdExceeded(lockId, msg.sender); } - if(lockId == 0){ + if (lockId == 0) { revert ZeroLockId(); } _updateStreamRPS(); @@ -277,21 +312,28 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A _moveAllStreamRewardsToPending(msg.sender, lockId); } - function claimAllLockRewardsForStream(uint256 streamId) public override pausable(1) { + function claimAllLockRewardsForStream(uint256 streamId) external override pausable(1) { _updateStreamRPS(); _moveAllLockPositionRewardsToPending(msg.sender, streamId); } - function withdrawStream(uint256 streamId) public override pausable(1) { + function withdrawStream(uint256 streamId) external override pausable(1) { User storage userAccount = users[msg.sender]; - require(userAccount.pendings[streamId] != 0, "no pendings"); - require(block.timestamp > userAccount.releaseTime[streamId], "not released"); - require(streams[streamId].status == StreamStatus.ACTIVE, "stream inactive"); + if (userAccount.pendings[streamId] == 0) { + revert NoPendings(); + } + + if (block.timestamp <= userAccount.releaseTime[streamId]) { + revert NotReleased(); + } + + if (streams[streamId].status != StreamStatus.ACTIVE) { + revert StreamInactive(); + } _withdraw(streamId); } - - function withdrawAllStreams() public override pausable(1) { + function withdrawAllStreams() external override pausable(1) { User storage userAccount = users[msg.sender]; for (uint256 i; i < streams.length; i++) { if (userAccount.pendings[i] != 0 && block.timestamp > userAccount.releaseTime[i] && streams[i].status == StreamStatus.ACTIVE) { @@ -299,11 +341,12 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } } } + /** * @dev Disregard rewards for emergency unlock and withdraw */ - function emergencyUnlockAndWithdraw() public override { - if(paused == 0){ + function emergencyUnlockAndWithdraw() external override { + if (paused == 0) { revert NotPaused(); } uint256 numberOfLocks = locks[msg.sender].length; @@ -318,19 +361,19 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Vault can be updated only if the contract is at paused state. * Only Admin that is, Multisig at the time of deployment can update Vault */ - function updateVault(address _vault) public override onlyRole(DEFAULT_ADMIN_ROLE) { + function updateVault(address _vault) external override onlyRole(DEFAULT_ADMIN_ROLE) { // enforce pausing this contract before updating the address. // This mitigates the risk of future invalid reward claims - if (paused == 0){ + if (paused == 0) { revert NotPaused(); } - if(_vault == address(0)){ + if (_vault == address(0)) { revert ZeroAddress(); } - if(!IERC165Upgradeable(_vault).supportsInterface(type(IVault).interfaceId)){ + if (!IERC165Upgradeable(_vault).supportsInterface(type(IVault).interfaceId)) { revert VaultNotSupported(_vault); } - if(!IVault(vault).migrated()){ + if (!IVault(vault).migrated()) { revert VaultNotMigrated(vault); } vault = _vault; @@ -340,54 +383,19 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Penalty accrued due to early unlocking can be withdrawn to some address, most likely the treasury. * Address with TREASURY_ROLE can access this function, which is Multisig at time of deployment */ - function withdrawPenalty(address penaltyReceiver) public override pausable(1) onlyRole(TREASURY_ROLE) { - if(totalPenaltyBalance == 0){ + function withdrawPenalty(address penaltyReceiver) external override pausable(1) onlyRole(TREASURY_ROLE) { + if (totalPenaltyBalance == 0) { revert ZeroPenalty(); } _withdrawPenalty(penaltyReceiver); } - function _createLock( - uint256 amount, - uint256 lockPeriod, - address account - ) internal{ - require(lockPeriod >= minLockPeriod, "min lock"); - require(locks[account].length <= maxLockPositions, "max locks"); - require(amount > 0, "amount 0"); - require(lockPeriod <= maxLockPeriod, "max time"); - IERC20(mainToken).safeTransferFrom(msg.sender,address(this),amount); - _updateStreamRPS(); - _lock(account, amount, lockPeriod); - _transfer(amount,mainToken); - } - - function _verifyUnlock(uint256 lockId) internal view { - if(lockId == 0){ - revert ZeroLockId(); - } - if(lockId > locks[msg.sender].length){ - revert MaxLockIdExceeded(lockId, msg.sender); - } - LockedBalance storage lock = locks[msg.sender][lockId - 1]; - require(lock.owner == msg.sender, "bad owner"); - if(lock.amountOfToken == 0){ - revert ZeroLocked(lockId); - } - } - - function _transfer(uint256 _amount, address _token) internal{ - IERC20(_token).safeApprove(vault,0); - IERC20(_token).safeApprove(vault,_amount); - IVault(vault).deposit(_token, _amount); - } - /** * @dev This allows for setting up minimum locking period. * Only admin which is Multisig at deployment can call this */ - function setMinimumLockPeriod(uint256 _minLockPeriod) public override onlyRole(DEFAULT_ADMIN_ROLE){ - if(_minLockPeriod > maxLockPeriod){ + function setMinimumLockPeriod(uint256 _minLockPeriod) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (_minLockPeriod > maxLockPeriod) { revert MaxLockPeriodExceeded(); } minLockPeriod = _minLockPeriod; @@ -397,11 +405,70 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev This allows for setting up maximum lock positions. * Only admin which is Multisig at deployment can call this */ - function setMaxLockPositions(uint256 newMaxLockPositions) public override onlyRole(DEFAULT_ADMIN_ROLE){ - if(newMaxLockPositions < maxLockPositions){ + function setMaxLockPositions(uint256 newMaxLockPositions) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (newMaxLockPositions < maxLockPositions) { revert BadMaxLockPositions(); } maxLockPositions = newMaxLockPositions; } - + + function _createLock(uint256 amount, uint256 lockPeriod, address account) internal { + if (lockPeriod < minLockPeriod) { + revert MinLockPeriodNotMet(); + } + if (locks[account].length > maxLockPositions) { + revert MaxLockPositionsReached(); + } + if (amount == 0) { + revert ZeroAmount(); + } + if (lockPeriod > maxLockPeriod) { + revert MaxLockPeriodExceeded(); + } + IERC20(mainToken).safeTransferFrom(msg.sender, address(this), amount); + _updateStreamRPS(); + _lock(account, amount, lockPeriod); + _transfer(amount, mainToken); + } + + function _transfer(uint256 _amount, address _token) internal { + IERC20(_token).safeApprove(vault, 0); + IERC20(_token).safeApprove(vault, _amount); + IVault(vault).deposit(_token, _amount); + } + + function _verifyStream(Stream memory stream, uint256 rewardTokenAmount) internal view { + if (stream.status != StreamStatus.PROPOSED) { + revert NotProposed(); + } + if (stream.schedule.time[0] < block.timestamp) { + revert ProposalExpired(); + } + if (stream.owner != msg.sender) { + revert NotOwner(); + } + + if (rewardTokenAmount > stream.maxDepositAmount) { + revert RewardsTooHigh(); + } + if (rewardTokenAmount < stream.minDepositAmount) { + revert RewardsTooLow(); + } + } + + function _verifyUnlock(uint256 lockId) internal view { + if (lockId == 0) { + revert ZeroLockId(); + } + if (lockId > locks[msg.sender].length) { + revert MaxLockIdExceeded(lockId, msg.sender); + } + LockedBalance storage lock = locks[msg.sender][lockId - 1]; + if (lock.owner != msg.sender) { + revert NotLockOwner(); + } + if (lock.amountOfToken == 0) { + revert ZeroLocked(lockId); + } + } } diff --git a/contracts/dao/staking/packages/StakingInternals.sol b/contracts/dao/staking/packages/StakingInternals.sol index 4135563..64d57d0 100644 --- a/contracts/dao/staking/packages/StakingInternals.sol +++ b/contracts/dao/staking/packages/StakingInternals.sol @@ -12,34 +12,26 @@ import "../../tokens/IVMainToken.sol"; import "../../../common/math/BoringMath.sol"; import "../../../common/math/FullMath.sol"; - contract StakingInternals is RewardsInternals { // solhint-disable not-rely-on-time error ZeroAddress(); error ZeroLocked(uint256 lockId); error ZeroTotalStakedToken(); + error InvalidShareWeights(); + error InvalidPenaltyWeights(); + error IncorrectWeight(); + error ZeroCoefficient(); + function _initializeStaking( address _mainToken, address _voteToken, - Weight calldata _weight, + Weight memory _weight, address _vault, uint256 _maxLockPositions, uint256 _voteShareCoef, uint256 _voteLockCoef ) internal { - if(_mainToken == address(0x00)){ - revert ZeroAddress(); - } - if(_voteToken == address(0x00)){ - revert ZeroAddress(); - } - if(_vault == address(0x00)){ - revert ZeroAddress(); - } - require(_weight.maxWeightShares > _weight.minWeightShares, "bad share"); - require(_weight.maxWeightPenalty > _weight.minWeightPenalty, "bad penalty"); - require(_weight.penaltyWeightMultiplier * _weight.maxWeightPenalty <= 100000, "wrong weight"); - require(_voteLockCoef != 0, "zero coef"); + _verifyStaking(_mainToken, _voteToken, _weight, _vault, _voteLockCoef); mainToken = _mainToken; voteToken = _voteToken; weight = _weight; @@ -49,11 +41,7 @@ contract StakingInternals is RewardsInternals { voteLockCoef = _voteLockCoef; } - function _lock( - address account, - uint256 amount, - uint256 lockPeriod - ) internal { + function _lock(address account, uint256 amount, uint256 lockPeriod) internal { uint256 nVoteToken; User storage userAccount = users[account]; if (lockPeriod > 0) { @@ -87,19 +75,14 @@ contract StakingInternals is RewardsInternals { * @notice If the lock position is completely unlocked then the last lock is swapped with current locked * and last lock is popped off. */ - function _unlock( - uint256 stakeValue, - uint256 amount, - uint256 lockId, - address account - ) internal { + function _unlock(uint256 stakeValue, uint256 amount, uint256 lockId, address account) internal { User storage userAccount = users[account]; LockedBalance storage updateLock = locks[account][lockId - 1]; - if(totalAmountOfStakedToken == 0){ + if (totalAmountOfStakedToken == 0) { revert ZeroTotalStakedToken(); } - + uint256 nVoteToken = updateLock.amountOfVoteToken; /// if you unstake, early or partial or complete, /// the number of vote tokens for lock position is set to zero @@ -130,12 +113,7 @@ contract StakingInternals is RewardsInternals { * @notice the amount of stream shares you receive decreases from 100% to 25% * @notice the amount of stream shares you receive depends upon when in the timeline you have staked */ - function _stake( - address account, - uint256 amount, - uint256 nVoteToken, - uint256 lockId - ) internal { + function _stake(address account, uint256 amount, uint256 nVoteToken, uint256 lockId) internal { User storage userAccount = users[account]; LockedBalance storage lock = locks[account][lockId - 1]; @@ -154,12 +132,7 @@ contract StakingInternals is RewardsInternals { emit Staked(account, amount, weightedAmountOfSharesPerStream, nVoteToken, lockId, lock.end); } - function _unstake( - uint256 amount, - uint256 stakeValue, - uint256 lockId, - address account - ) internal { + function _unstake(uint256 amount, uint256 stakeValue, uint256 lockId, address account) internal { User storage userAccount = users[account]; LockedBalance storage updateLock = locks[account][lockId - 1]; totalAmountOfStakedToken -= stakeValue; @@ -183,12 +156,7 @@ contract StakingInternals is RewardsInternals { } } - function _restakeThePosition( - uint256 amountToRestake, - uint256 lockId, - LockedBalance storage updateLock, - User storage userAccount - ) internal { + function _restakeThePosition(uint256 amountToRestake, uint256 lockId, LockedBalance storage updateLock, User storage userAccount) internal { totalAmountOfStakedToken += amountToRestake; updateLock.amountOfToken += BoringMath.to128(amountToRestake); ///@notice if you unstake, early or partial or complete, @@ -228,11 +196,7 @@ contract StakingInternals is RewardsInternals { totalPenaltyBalance += penalty; } - function _removeLockPosition( - User storage userAccount, - address account, - uint256 lockId - ) internal { + function _removeLockPosition(User storage userAccount, address account, uint256 lockId) internal { uint256 streamsLength = streams.length; uint256 lastLockId = locks[account].length; if (lastLockId != lockId && lastLockId > 1) { @@ -263,11 +227,7 @@ contract StakingInternals is RewardsInternals { IVault(vault).payRewards(accountTo, mainToken, pendingPenalty); } - function _weightedShares( - uint256 amountOfTokenShares, - uint256 nVoteToken, - uint256 timestamp - ) internal view returns (uint256) { + function _weightedShares(uint256 amountOfTokenShares, uint256 nVoteToken, uint256 timestamp) internal view returns (uint256) { ///@notice Shares accomodate vote the amount of tokenShares and vote Tokens to be released ///@notice This formula makes it so that both the time locked for Main token and the amount of token locked /// is used to calculate rewards @@ -301,4 +261,30 @@ contract StakingInternals is RewardsInternals { (weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) / maxLockPeriod); } + + // solhint-disable code-complexity + function _verifyStaking(address _mainToken, address _voteToken, Weight memory _weight, address _vault, uint256 _voteLockCoef) internal pure { + if (_mainToken == address(0x00)) { + revert ZeroAddress(); + } + if (_voteToken == address(0x00)) { + revert ZeroAddress(); + } + if (_vault == address(0x00)) { + revert ZeroAddress(); + } + if (_weight.maxWeightShares <= _weight.minWeightShares) { + revert InvalidShareWeights(); + } + if (_weight.maxWeightPenalty <= _weight.minWeightPenalty) { + revert InvalidPenaltyWeights(); + } + if (_weight.penaltyWeightMultiplier * _weight.maxWeightPenalty > 100000) { + revert IncorrectWeight(); + } + if (_voteLockCoef == 0) { + revert ZeroCoefficient(); + } + } + // solhint-enable code-complexity } diff --git a/contracts/dao/staking/vault/interfaces/IVault.sol b/contracts/dao/staking/vault/interfaces/IVault.sol index 26f23c8..bbeba8c 100644 --- a/contracts/dao/staking/vault/interfaces/IVault.sol +++ b/contracts/dao/staking/vault/interfaces/IVault.sol @@ -4,10 +4,7 @@ pragma solidity 0.8.16; interface IVault { function initVault(address _admin, address[] calldata supportedTokens) external; - function deposit( - address _token, - uint256 _amount - ) external; + function deposit(address _token, uint256 _amount) external; function addRewardsOperator(address _rewardsOperator) external; @@ -15,21 +12,15 @@ interface IVault { function removeSupportedToken(address _token) external; - function payRewards( - address _user, - address _token, - uint256 _deposit - ) external; + function payRewards(address _user, address _token, uint256 _deposit) external; function migrate(address vaultPackageMigrateTo) external; function withdrawExtraSupportedTokens(address _withdrawTo) external; - - function withdrawExtraUnsupportedToken(address _token,address _withdrawTo) external; + + function withdrawExtraUnsupportedToken(address _token, address _withdrawTo) external; function isSupportedToken(address token) external view returns (bool); function migrated() external view returns (bool); - - } diff --git a/contracts/dao/staking/vault/packages/VaultPackage.sol b/contracts/dao/staking/vault/packages/VaultPackage.sol index ed4c8d7..8dd6c20 100644 --- a/contracts/dao/staking/vault/packages/VaultPackage.sol +++ b/contracts/dao/staking/vault/packages/VaultPackage.sol @@ -13,12 +13,24 @@ import "../../../../common/introspection/ERC165.sol"; // solhint-disable not-rely-on-time contract VaultPackage is IVault, IVaultEvents, AdminPausable { using SafeERC20 for IERC20; - bytes32 public constant REWARDS_OPERATOR_ROLE = keccak256("REWARDS_OPERATOR_ROLE"); + mapping(address => uint256) public deposited; mapping(address => bool) public override isSupportedToken; address[] public listOfSupportedTokens; bool public override migrated; + bytes32 public constant REWARDS_OPERATOR_ROLE = keccak256("REWARDS_OPERATOR_ROLE"); + + error NoRewardsOperatorRole(); + error UnsupportedToken(); + error AmountZero(); + error InsufficientDeposit(); + error VaultMigrated(); + error TokenAlreadyExists(); + error TokenInUse(); + error ZeroAddress(); + error RequiredPause(); + constructor() { _disableInitializers(); } @@ -34,16 +46,12 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { _grantRole(REWARDS_OPERATOR_ROLE, _rewardsOperator); } - function payRewards( - address _user, - address _token, - uint256 _amount - ) external override pausable(1) { - require(hasRole(REWARDS_OPERATOR_ROLE, msg.sender), "payRewards: No role"); - require(isSupportedToken[_token], "Unsupported token"); - require(_amount != 0, "amount zero"); - require(deposited[_token] >= _amount, "payRewards: not enough deposit"); - require(!migrated,"vault already migrated"); + function payRewards(address _user, address _token, uint256 _amount) external override pausable(1) { + if (!hasRole(REWARDS_OPERATOR_ROLE, msg.sender)) revert NoRewardsOperatorRole(); + if (!isSupportedToken[_token]) revert UnsupportedToken(); + if (_amount == 0) revert AmountZero(); + if (deposited[_token] < _amount) revert InsufficientDeposit(); + if (migrated) revert VaultMigrated(); uint256 previousBalance = IERC20(_token).balanceOf(address(this)); IERC20(_token).safeTransfer(_user, _amount); @@ -52,14 +60,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { deposited[_token] -= trueDeposit; } - function deposit( - address _token, - uint256 _amount - ) external override pausable(1) { - require(hasRole(REWARDS_OPERATOR_ROLE, msg.sender), "deposit: No role"); - require(isSupportedToken[_token], "Unsupported token"); - require(!migrated,"vault already migrated"); - + function deposit(address _token, uint256 _amount) external override pausable(1) { + if (!hasRole(REWARDS_OPERATOR_ROLE, msg.sender)) revert NoRewardsOperatorRole(); + if (!isSupportedToken[_token]) revert UnsupportedToken(); + if (migrated) revert VaultMigrated(); + uint256 previousBalance = IERC20(_token).balanceOf(address(this)); IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); uint256 newBalance = IERC20(_token).balanceOf(address(this)); @@ -69,52 +74,53 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { /// @notice adds token as a supported rewards token by Vault /// supported tokens means any future stream token should be - /// whitelisted here + /// allowlisted here /// @param _token stream ERC20 token address function addSupportedToken(address _token) external override onlyRole(DEFAULT_ADMIN_ROLE) { - require(!migrated,"vault already migrated"); + if (migrated) revert VaultMigrated(); _addSupportedToken(_token); } /// @notice removed token as a supported rewards token by Treasury /// @param _token stream ERC20 token address function removeSupportedToken(address _token) external override onlyRole(DEFAULT_ADMIN_ROLE) { - require(!migrated,"vault already migrated"); - require(isSupportedToken[_token], "Token does not exist"); - require(deposited[_token] == 0, "Token is still in use"); - + if (migrated) revert VaultMigrated(); + if (!isSupportedToken[_token]) revert UnsupportedToken(); + if (deposited[_token] > 0) revert TokenInUse(); + isSupportedToken[_token] = false; _removeToken(_token); emit TokenRemoved(_token, msg.sender, block.timestamp); } function withdrawExtraSupportedTokens(address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) { - for(uint i = 0; i < listOfSupportedTokens.length;i++){ + for (uint i = 0; i < listOfSupportedTokens.length; i++) { uint256 balanceToWithdraw; address _token = listOfSupportedTokens[i]; uint256 balanceInContract = IERC20(_token).balanceOf(address(this)); - if(balanceInContract > deposited[_token]){ - balanceToWithdraw = balanceInContract - deposited[_token]; + if (balanceInContract > deposited[_token]) { + balanceToWithdraw = balanceInContract - deposited[_token]; } - if(balanceToWithdraw > 0){ + if (balanceToWithdraw > 0) { IERC20(_token).safeTransfer(_withdrawTo, balanceToWithdraw); - } - } + } + } } - function withdrawExtraUnsupportedToken(address _token,address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) { - require(!isSupportedToken[_token],"token is supported"); + function withdrawExtraUnsupportedToken(address _token, address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (isSupportedToken[_token]) revert TokenAlreadyExists(); uint256 balanceInContract = IERC20(_token).balanceOf(address(this)); - if(balanceInContract > 0){ + if (balanceInContract > 0) { IERC20(_token).safeTransfer(_withdrawTo, balanceInContract); } } /// @notice we believe newVaultPackage is safe function migrate(address newVaultPackage) external override onlyRole(DEFAULT_ADMIN_ROLE) { - require(!migrated, "vault already migrated"); - require(paused != 0, "required pause"); - require(newVaultPackage != address(0), "withdrawTo: Zero addr"); + if (migrated) revert VaultMigrated(); + if (paused == 0) revert RequiredPause(); + if (newVaultPackage == address(0)) revert ZeroAddress(); + for (uint256 i = 0; i < listOfSupportedTokens.length; i++) { address token = listOfSupportedTokens[i]; deposited[token] = 0; @@ -125,12 +131,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { } function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { - return interfaceId == type(IERC165).interfaceId || - interfaceId == type(IVault).interfaceId; + return interfaceId == type(IERC165).interfaceId || interfaceId == type(IVault).interfaceId; } function _addSupportedToken(address _token) internal { - require(!isSupportedToken[_token], "Token already exists"); + if (isSupportedToken[_token]) revert TokenAlreadyExists(); isSupportedToken[_token] = true; listOfSupportedTokens.push(_token); emit TokenAdded(_token, msg.sender, block.timestamp); @@ -145,6 +150,4 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { } listOfSupportedTokens.pop(); } - - } diff --git a/contracts/dao/test/ERC20Rewards1.sol b/contracts/dao/test/ERC20Rewards1.sol index af025a8..0d51bad 100644 --- a/contracts/dao/test/ERC20Rewards1.sol +++ b/contracts/dao/test/ERC20Rewards1.sol @@ -52,12 +52,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * All two of these values are immutable: they can only be set once during * construction. */ - constructor( - string memory name_, - string memory symbol_, - uint256 totalSupply_, - address _multiSigTreasury - ) { + constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address _multiSigTreasury) { _name = name_; _symbol = symbol_; _totalSupply = totalSupply_; @@ -112,11 +107,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -235,11 +226,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { + function _transfer(address from, address to, uint256 amount) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); @@ -319,11 +306,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); @@ -339,11 +322,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * Might emit an {Approval} event. */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); @@ -367,11 +346,7 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes @@ -387,9 +362,5 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} } diff --git a/contracts/dao/test/ERC20Rewards2.sol b/contracts/dao/test/ERC20Rewards2.sol index 8562b48..c3845db 100644 --- a/contracts/dao/test/ERC20Rewards2.sol +++ b/contracts/dao/test/ERC20Rewards2.sol @@ -53,12 +53,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * construction. */ - constructor( - string memory name_, - string memory symbol_, - uint256 totalSupply_, - address issuer - ) { + constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address issuer) { _name = name_; _symbol = symbol_; _totalSupply = totalSupply_; @@ -113,11 +108,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -236,11 +227,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { + function _transfer(address from, address to, uint256 amount) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); @@ -320,11 +307,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); @@ -340,11 +323,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * Might emit an {Approval} event. */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); @@ -368,11 +347,7 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes @@ -388,9 +363,5 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} } diff --git a/contracts/dao/test/dex/IUniswapV2Router01.sol b/contracts/dao/test/dex/IUniswapV2Router01.sol index 4619967..bd22d4a 100644 --- a/contracts/dao/test/dex/IUniswapV2Router01.sol +++ b/contracts/dao/test/dex/IUniswapV2Router01.sol @@ -1,7 +1,12 @@ +// SPDX-License-Identifier: MIT +// Original Copyright Uniswap +// Copyright Fathom 2022 + pragma solidity >=0.6.2; interface IUniswapV2Router01 { function factory() external pure returns (address); + function WETH() external pure returns (address); function addLiquidity( @@ -14,6 +19,7 @@ interface IUniswapV2Router01 { address to, uint deadline ) external returns (uint amountA, uint amountB, uint liquidity); + function addLiquidityETH( address token, uint amountTokenDesired, @@ -22,6 +28,7 @@ interface IUniswapV2Router01 { address to, uint deadline ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + function removeLiquidity( address tokenA, address tokenB, @@ -31,6 +38,7 @@ interface IUniswapV2Router01 { address to, uint deadline ) external returns (uint amountA, uint amountB); + function removeLiquidityETH( address token, uint liquidity, @@ -39,6 +47,7 @@ interface IUniswapV2Router01 { address to, uint deadline ) external returns (uint amountToken, uint amountETH); + function removeLiquidityWithPermit( address tokenA, address tokenB, @@ -47,8 +56,12 @@ interface IUniswapV2Router01 { uint amountBMin, address to, uint deadline, - bool approveMax, uint8 v, bytes32 r, bytes32 s + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s ) external returns (uint amountA, uint amountB); + function removeLiquidityETHWithPermit( address token, uint liquidity, @@ -56,8 +69,12 @@ interface IUniswapV2Router01 { uint amountETHMin, address to, uint deadline, - bool approveMax, uint8 v, bytes32 r, bytes32 s + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s ) external returns (uint amountToken, uint amountETH); + function swapExactTokensForTokens( uint amountIn, uint amountOutMin, @@ -65,6 +82,7 @@ interface IUniswapV2Router01 { address to, uint deadline ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( uint amountOut, uint amountInMax, @@ -72,24 +90,44 @@ interface IUniswapV2Router01 { address to, uint deadline ) external returns (uint[] memory amounts); - function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) - external - payable - returns (uint[] memory amounts); - function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) - external - returns (uint[] memory amounts); - function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) - external - returns (uint[] memory amounts); - function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) - external - payable - returns (uint[] memory amounts); + + function swapExactETHForTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable returns (uint[] memory amounts); + + function swapTokensForExactETH( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapExactTokensForETH( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapETHForExactTokens( + uint amountOut, + address[] calldata path, + address to, + uint deadline + ) external payable returns (uint[] memory amounts); function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); } diff --git a/contracts/dao/test/dex/IUniswapV2Router02.sol b/contracts/dao/test/dex/IUniswapV2Router02.sol index 1fc7b0a..d796292 100644 --- a/contracts/dao/test/dex/IUniswapV2Router02.sol +++ b/contracts/dao/test/dex/IUniswapV2Router02.sol @@ -1,6 +1,10 @@ +// SPDX-License-Identifier: MIT +// Original Copyright Uniswap +// Copyright Fathom 2022 + pragma solidity >=0.6.2; -import './IUniswapV2Router01.sol'; +import "./IUniswapV2Router01.sol"; interface IUniswapV2Router02 is IUniswapV2Router01 { function removeLiquidityETHSupportingFeeOnTransferTokens( @@ -11,6 +15,7 @@ interface IUniswapV2Router02 is IUniswapV2Router01 { address to, uint deadline ) external returns (uint amountETH); + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( address token, uint liquidity, @@ -18,7 +23,10 @@ interface IUniswapV2Router02 is IUniswapV2Router01 { uint amountETHMin, address to, uint deadline, - bool approveMax, uint8 v, bytes32 r, bytes32 s + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s ) external returns (uint amountETH); function swapExactTokensForTokensSupportingFeeOnTransferTokens( @@ -28,12 +36,14 @@ interface IUniswapV2Router02 is IUniswapV2Router01 { address to, uint deadline ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, diff --git a/contracts/dao/test/stablecoin/IProxyRegistry.sol b/contracts/dao/test/stablecoin/IProxyRegistry.sol index 3971444..ef801f6 100644 --- a/contracts/dao/test/stablecoin/IProxyRegistry.sol +++ b/contracts/dao/test/stablecoin/IProxyRegistry.sol @@ -7,4 +7,4 @@ interface IProxyRegistry { function build(address) external returns (address); function isProxy(address) external view returns (bool); -} \ No newline at end of file +} diff --git a/contracts/dao/test/token-factory/ERC20Factory.sol b/contracts/dao/test/token-factory/ERC20Factory.sol index 9ef6c05..462d6a8 100644 --- a/contracts/dao/test/token-factory/ERC20Factory.sol +++ b/contracts/dao/test/token-factory/ERC20Factory.sol @@ -6,11 +6,7 @@ import "../../tokens/ERC20/ERC20.sol"; import "../../../common/access/AccessControl.sol"; contract Token is ERC20 { - constructor( - string memory _name, - string memory _ticker, - uint256 _supply - ) ERC20(_name, _ticker) { + constructor(string memory _name, string memory _ticker, uint256 _supply) ERC20(_name, _ticker) { _mint(msg.sender, _supply); } } @@ -26,11 +22,7 @@ contract ERC20Factory is IERC20Factory, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function deployToken( - string calldata _name, - string calldata _ticker, - uint256 _supply - ) public override onlyRole(DEPLOYER_ROLE) returns (address) { + function deployToken(string calldata _name, string calldata _ticker, uint256 _supply) public override onlyRole(DEPLOYER_ROLE) returns (address) { Token token = new Token(_name, _ticker, _supply); tokens.push(address(token)); diff --git a/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol b/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol index 36efced..b679e00 100644 --- a/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol +++ b/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol @@ -2,9 +2,5 @@ pragma solidity 0.8.16; interface IERC20Factory { - function deployToken( - string calldata _name, - string calldata _ticker, - uint256 _supply - ) external returns (address); + function deployToken(string calldata _name, string calldata _ticker, uint256 _supply) external returns (address); } diff --git a/contracts/dao/test/token-timelock/TokenTimelock.sol b/contracts/dao/test/token-timelock/TokenTimelock.sol index 9a62734..9b43a84 100644 --- a/contracts/dao/test/token-timelock/TokenTimelock.sol +++ b/contracts/dao/test/token-timelock/TokenTimelock.sol @@ -26,11 +26,7 @@ contract TokenTimelock is ITokenTimelock { // timestamp when token release is enabled uint256 private immutable _releaseTime; - constructor( - IERC20 token_, - address beneficiary_, - uint256 releaseTime_ - ) { + constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) { // solhint-disable-next-line require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time"); _token = token_; diff --git a/contracts/dao/test/token-timelock/TokenTimelockFactory.sol b/contracts/dao/test/token-timelock/TokenTimelockFactory.sol index 6f709c4..4f7525a 100644 --- a/contracts/dao/test/token-timelock/TokenTimelockFactory.sol +++ b/contracts/dao/test/token-timelock/TokenTimelockFactory.sol @@ -18,12 +18,10 @@ contract TokenTimelockFactory is ITokenTimelockFactory, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function deployTokenTimelocks(address[] memory beneficiaries, uint256[] memory releaseTimes) - public - override - onlyRole(DEPLOYER_ROLE) - returns (address[] memory) - { + function deployTokenTimelocks( + address[] memory beneficiaries, + uint256[] memory releaseTimes + ) public override onlyRole(DEPLOYER_ROLE) returns (address[] memory) { uint256 length = beneficiaries.length; require(length == releaseTimes.length, "Wrong lengths"); diff --git a/contracts/dao/tokens/ERC20/ERC20.sol b/contracts/dao/tokens/ERC20/ERC20.sol index 53e07ce..b1836e2 100644 --- a/contracts/dao/tokens/ERC20/ERC20.sol +++ b/contracts/dao/tokens/ERC20/ERC20.sol @@ -17,28 +17,33 @@ contract ERC20 is Context, IERC20, IERC20Metadata { string internal _name; string internal _symbol; + error ERC20TransferToZeroAddress(); + error ERC20TransferExceedsBalance(); + error ERC20TransferAmountZero(); + error ERC20ApproveFromZeroAddress(); + error ERC20ApproveToZeroAddress(); + error ERC20InsufficientAllowance(); + error ERC20DecreasedAllowanceBelowZero(); + error ERC20TransferFromZeroAddress(); + constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } - function transfer(address to, uint256 amount) public virtual override returns (bool) { + function transfer(address to, uint256 amount) external virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } - function approve(address spender, uint256 amount) public virtual override returns (bool) { + function approve(address spender, uint256 amount) external virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { + function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -46,17 +51,19 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } // solhint-disable-next-line - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + function increaseAllowance(address spender, uint256 addedValue) external virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, allowance(owner, spender) + addedValue); return true; } // solhint-disable-next-line - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + if (currentAllowance < subtractedValue) { + revert ERC20DecreasedAllowanceBelowZero(); + } unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } @@ -64,42 +71,44 @@ contract ERC20 is Context, IERC20, IERC20Metadata { return true; } - function allowance(address owner, address spender) public view virtual override returns (uint256) { - return _allowances[owner][spender]; - } - - function name() public view virtual override returns (string memory) { + function name() external view virtual override returns (string memory) { return _name; } - function symbol() public view virtual override returns (string memory) { + function symbol() external view virtual override returns (string memory) { return _symbol; } - function decimals() public view virtual override returns (uint8) { + function decimals() external view virtual override returns (uint8) { return 18; } + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account]; + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; } - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); + function _transfer(address from, address to, uint256 amount) internal virtual { + if (from == address(0)) { + revert ERC20TransferFromZeroAddress(); + } + if (to == address(0)) { + revert ERC20TransferToZeroAddress(); + } _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + if (fromBalance < amount) { + revert ERC20TransferExceedsBalance(); + } unchecked { _balances[from] = fromBalance - amount; } @@ -111,7 +120,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } function _mint(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); + if (account == address(0)) { + revert ERC20TransferToZeroAddress(); + } _beforeTokenTransfer(address(0), account, amount); @@ -123,12 +134,16 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } function _burn(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); + if (account == address(0)) { + revert ERC20TransferFromZeroAddress(); + } _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + if (accountBalance < amount) { + revert ERC20TransferExceedsBalance(); + } unchecked { _balances[account] = accountBalance - amount; } @@ -139,41 +154,31 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _afterTokenTransfer(account, address(0), amount); } - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); + function _approve(address owner, address spender, uint256 amount) internal virtual { + if (owner == address(0)) { + revert ERC20ApproveFromZeroAddress(); + } + if (spender == address(0)) { + revert ERC20ApproveToZeroAddress(); + } _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); + if (currentAllowance < amount) { + revert ERC20InsufficientAllowance(); + } unchecked { _approve(owner, spender, currentAllowance - amount); } } } - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} } diff --git a/contracts/dao/tokens/ERC20/IERC20.sol b/contracts/dao/tokens/ERC20/IERC20.sol index 7476ba8..2b8aef4 100644 --- a/contracts/dao/tokens/ERC20/IERC20.sol +++ b/contracts/dao/tokens/ERC20/IERC20.sol @@ -12,11 +12,7 @@ interface IERC20 { function approve(address spender, uint256 amount) external returns (bool); - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); diff --git a/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol b/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol index 21c14f7..fa21dba 100644 --- a/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol +++ b/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol @@ -16,41 +16,40 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { mapping(address => Counters.Counter) private _nonces; // solhint-disable-next-line var-name-mixedcase - bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT; // solhint-disable-next-line var-name-mixedcase - bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT; + bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) EIP712(name_, "1") {} + error ExpiredDeadline(); + error InvalidSignature(); - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view override returns (bytes32) { - return _domainSeparatorV4(); - } + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) EIP712(name_, "1") {} - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual override { - // solhint-disable-next-line - require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external virtual override { + // solhint-disable-next-line not-rely-on-time + if (block.timestamp > deadline) { + revert ExpiredDeadline(); + } bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); - require(signer == owner, "ERC20Permit: invalid signature"); + if (signer != owner) { + revert InvalidSignature(); + } _approve(owner, spender, value); } - function nonces(address owner) public view virtual override returns (uint256) { + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + function nonces(address owner) external view virtual override returns (uint256) { return _nonces[owner].current(); } diff --git a/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol b/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol index e03c6dd..825d944 100644 --- a/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol +++ b/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol @@ -16,63 +16,71 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { uint224 votes; } - bytes32 private constant _DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - mapping(address => address) private _delegates; mapping(address => Checkpoint[]) private _checkpoints; Checkpoint[] private _totalSupplyCheckpoints; + bytes32 private constant _DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + error InvalidNonce(); + error SignatureExpired(); + error BlockNotYetMined(); + error TotalSupplyOverflowsVotes(); + constructor(string memory name_, string memory symbol_) ERC20Permit(name_, symbol_) {} - function delegate(address delegatee) public virtual override { + function delegate(address delegatee) external virtual override { _delegate(_msgSender(), delegatee); } - function delegateBySig( - address delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual override { - // solhint-disable-next-line - require(block.timestamp <= expiry, "ERC20Votes: signature expired"); + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external virtual override { + // solhint-disable-next-line not-rely-on-time + if (block.timestamp > expiry) { + revert SignatureExpired(); + } address signer = ECDSA.recover(_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), v, r, s); - require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce"); + if (nonce != _useNonce(signer)) { + revert InvalidNonce(); + } _delegate(signer, delegatee); } - function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) { + function checkpoints(address account, uint32 pos) external view virtual returns (Checkpoint memory) { return _checkpoints[account][pos]; } - function numCheckpoints(address account) public view virtual returns (uint32) { + function numCheckpoints(address account) external view virtual returns (uint32) { return SafeCast.toUint32(_checkpoints[account].length); } - function delegates(address account) public view virtual override returns (address) { - return _delegates[account]; - } - - function getVotes(address account) public view virtual override returns (uint256) { + function getVotes(address account) external view virtual override returns (uint256) { uint256 pos = _checkpoints[account].length; return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; } - function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { - require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + function getPastVotes(address account, uint256 blockNumber) external view virtual override returns (uint256) { + if (blockNumber >= block.number) { + revert BlockNotYetMined(); + } return _checkpointsLookup(_checkpoints[account], blockNumber); } - function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { - require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + function getPastTotalSupply(uint256 blockNumber) external view virtual override returns (uint256) { + if (blockNumber >= block.number) { + revert BlockNotYetMined(); + } return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); } + function delegates(address account) public view virtual override returns (address) { + return _delegates[account]; + } + function _mint(address account, uint256 amount) internal virtual override { super._mint(account, amount); - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); + if (totalSupply() > _maxSupply()) { + revert TotalSupplyOverflowsVotes(); + } _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); } @@ -83,11 +91,7 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); } - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override { + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override { super._afterTokenTransfer(from, to, amount); _moveVotingPower(delegates(from), delegates(to), amount); @@ -107,11 +111,7 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { return type(uint224).max; } - function _moveVotingPower( - address src, - address dst, - uint256 amount - ) private { + function _moveVotingPower(address src, address dst, uint256 amount) private { if (src != dst && amount > 0) { if (src != address(0)) { (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount); diff --git a/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol b/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol index 8e87e34..dd45b20 100644 --- a/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol +++ b/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol @@ -5,15 +5,7 @@ pragma solidity 0.8.16; interface IERC20Permit { - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; function nonces(address owner) external view returns (uint256); diff --git a/contracts/dao/tokens/IVMainToken.sol b/contracts/dao/tokens/IVMainToken.sol index 12d8d7c..cf63716 100644 --- a/contracts/dao/tokens/IVMainToken.sol +++ b/contracts/dao/tokens/IVMainToken.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.16; interface IVMainToken { - event MemberAddedToWhitelist(address _member); - event MemberRemovedFromWhitelist(address _member); + event MemberAddedToAllowlist(address _member); + event MemberRemovedFromAllowlist(address _member); function initToken(address _admin, address _minter) external; diff --git a/contracts/dao/tokens/MainToken.sol b/contracts/dao/tokens/MainToken.sol index f3b8072..ec95974 100644 --- a/contracts/dao/tokens/MainToken.sol +++ b/contracts/dao/tokens/MainToken.sol @@ -6,12 +6,7 @@ pragma solidity 0.8.16; import "./ERC20/ERC20.sol"; contract MainToken is ERC20 { - constructor( - string memory name_, - string memory symbol_, - uint256 totalSupply_, - address issuer - ) ERC20(name_, symbol_) { + constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address issuer) ERC20(name_, symbol_) { _totalSupply = totalSupply_; _balances[issuer] = totalSupply_; diff --git a/contracts/dao/tokens/VMainToken.sol b/contracts/dao/tokens/VMainToken.sol index c5817dd..d2d302f 100644 --- a/contracts/dao/tokens/VMainToken.sol +++ b/contracts/dao/tokens/VMainToken.sol @@ -10,80 +10,76 @@ import "./IVMainToken.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; contract VMainToken is IVMainToken, Pausable, AccessControl, Initializable, ERC20Votes { + // Mapping to keep track of who is allowed to transfer voting tokens + mapping(address => bool) public isAllowListed; + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant WHITELISTER_ROLE = keccak256("WHITELISTER_ROLE"); - // Mapping to keep track of who is allowed to transfer voting tokens - mapping(address => bool) public isWhiteListed; + bytes32 public constant ALLOWLISTER_ROLE = keccak256("ALLOWLISTER_ROLE"); + + error AdminShouldBeDifferentThanMsgSender(); + error VMainTokenIsIntransferableUnlessTheSenderIsAllowlisted(); constructor(string memory name_, string memory symbol_) ERC20Votes(name_, symbol_) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function initToken(address _admin, address _minter) public override initializer onlyRole(DEFAULT_ADMIN_ROLE) { - require(_admin != msg.sender, "initToken: Admin should be different than msg.sender"); + function initToken(address _admin, address _minter) external override initializer onlyRole(DEFAULT_ADMIN_ROLE) { + if (_admin == msg.sender) revert AdminShouldBeDifferentThanMsgSender(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); _grantRole(PAUSER_ROLE, _admin); _grantRole(MINTER_ROLE, _minter); - _grantRole(WHITELISTER_ROLE, _admin); + _grantRole(ALLOWLISTER_ROLE, _admin); _revokeRole(DEFAULT_ADMIN_ROLE, msg.sender); - isWhiteListed[_minter] = true; - emit MemberAddedToWhitelist(_minter); + isAllowListed[_minter] = true; + emit MemberAddedToAllowlist(_minter); } - function grantMinterRole(address _minter) public override onlyRole(getRoleAdmin(MINTER_ROLE)) { + function grantMinterRole(address _minter) external override onlyRole(getRoleAdmin(MINTER_ROLE)) { _grantRole(MINTER_ROLE, _minter); - _addToWhitelist(_minter); + _addToAllowlist(_minter); } - function revokeMinterRole(address _minter) public override onlyRole(getRoleAdmin(MINTER_ROLE)) { + function revokeMinterRole(address _minter) external override onlyRole(getRoleAdmin(MINTER_ROLE)) { _revokeRole(MINTER_ROLE, _minter); - _removeFromWhitelist(_minter); - } - - function _addToWhitelist(address _toAdd) internal { - isWhiteListed[_toAdd] = true; - emit MemberAddedToWhitelist(_toAdd); + _removeFromAllowlist(_minter); } - function _removeFromWhitelist(address _toRemove) internal { - isWhiteListed[_toRemove] = false; - emit MemberRemovedFromWhitelist(_toRemove); - } - - function pause() public override onlyRole(PAUSER_ROLE) { + function pause() external override onlyRole(PAUSER_ROLE) { _pause(); } - function unpause() public override onlyRole(PAUSER_ROLE) { + function unpause() external override onlyRole(PAUSER_ROLE) { _unpause(); } - function mint(address to, uint256 amount) public override onlyRole(MINTER_ROLE) { + function mint(address to, uint256 amount) external override onlyRole(MINTER_ROLE) { _mint(to, amount); _delegate(to, to); } - function burn(address account, uint256 amount) public override onlyRole(MINTER_ROLE) { + function burn(address account, uint256 amount) external override onlyRole(MINTER_ROLE) { _burn(account, amount); } - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal override whenNotPaused { - require(isWhiteListed[msg.sender], "VMainToken: is intransferable unless the sender is whitelisted"); + function _addToAllowlist(address _toAdd) internal { + isAllowListed[_toAdd] = true; + emit MemberAddedToAllowlist(_toAdd); + } + + function _removeFromAllowlist(address _toRemove) internal { + isAllowListed[_toRemove] = false; + emit MemberRemovedFromAllowlist(_toRemove); + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) internal override whenNotPaused { + if (!isAllowListed[msg.sender]) revert VMainTokenIsIntransferableUnlessTheSenderIsAllowlisted(); super._beforeTokenTransfer(from, to, amount); } - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal override { + function _afterTokenTransfer(address from, address to, uint256 amount) internal override { super._afterTokenTransfer(from, to, amount); } } diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index 5145f88..c6dfaa3 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -7,12 +7,13 @@ import "./interfaces/IMultiSigWallet.sol"; import "../../common/Address.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +// solhint-disable not-rely-on-time contract MultiSigWallet is IMultiSigWallet { using Address for address; using EnumerableSet for EnumerableSet.UintSet; using EnumerableSet for EnumerableSet.AddressSet; - uint256 public constant MINIMUM_LIFETIME = 86400;//oneDay struct Transaction { address to; bool executed; @@ -22,62 +23,90 @@ contract MultiSigWallet is IMultiSigWallet { uint256 expireTimestamp; } - error TransactionRevered(bytes data); - - uint256 public constant MAX_OWNER_COUNT = 50; - - EnumerableSet.AddressSet owners; address public governor; - uint256 public numConfirmationsRequired; - mapping(address => bool) public isOwner; - - mapping(address => bytes32) internal whitelistedBytesCode; - Transaction[] public transactions; + + EnumerableSet.AddressSet internal owners; + mapping(address => bytes32) internal allowlistedBytesCode; mapping(address => EnumerableSet.UintSet) internal confirmedTransactionsByOwner; + uint256 public constant MINIMUM_LIFETIME = 86400; //oneDay + uint256 public constant MAX_OWNER_COUNT = 50; + + error TxDoesNotExist(); + error TxAlreadyExecuted(); + error TxAlreadyConfirmed(); + error TxExpired(); + error OnlyOwnerOrGov(); + error InvalidRequirement(); + error OwnerNotFound(); + error LifetimeMinimumNotMet(); + error InsufficientBalance(); + error InsufficientValue(); + error InvalidTargetCode(); + error OwnersLimitReached(); + error OwnersRequired(); + error InvalidOwner(); + error OwnerNotUnique(); + error TargetCodeChanged(); + error OwnerAlreadyExists(); + error TxNotConfirmed(); + modifier onlyOwnerOrGov() { - require(isOwner[msg.sender] || governor == msg.sender, "MultiSig: MultiSigWallet, onlyOwnerOrGov(): Neither owner nor governor"); + if (!isOwner[msg.sender] && governor != msg.sender) { + revert OnlyOwnerOrGov(); + } _; } modifier txExists(uint256 _txIndex) { - require(_txIndex < transactions.length, "MultiSig: tx does not exist"); + if (_txIndex >= transactions.length) { + revert TxDoesNotExist(); + } _; } modifier notExecuted(uint256 _txIndex) { - require(!transactions[_txIndex].executed, "MultiSig: tx already executed"); + if (transactions[_txIndex].executed) { + revert TxAlreadyExecuted(); + } _; } modifier notConfirmed(uint256 _txIndex) { - require(!confirmedTransactionsByOwner[msg.sender].contains(_txIndex), "MultiSig: tx already confirmed"); + if (confirmedTransactionsByOwner[msg.sender].contains(_txIndex)) { + revert TxAlreadyConfirmed(); + } _; } modifier notExpired(uint256 _txIndex) { - require(transactions[_txIndex].expireTimestamp >= block.timestamp || transactions[_txIndex].expireTimestamp == 0, "MultiSig: tx expired"); + if (transactions[_txIndex].expireTimestamp < block.timestamp && transactions[_txIndex].expireTimestamp != 0) { + revert TxExpired(); + } _; } modifier onlyWallet() { - require(msg.sender == address(this), "MultiSig: Only this wallet can use this funciton"); + if (msg.sender != address(this)) { + revert OnlyOwnerOrGov(); + } _; } modifier validRequirement(uint256 ownerCount, uint256 _required) { - require( - ownerCount > 0 && ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && ownerCount > 1 ? _required > 1 : _required > 0, - "MultiSig: Invalid requirement" - ); + if (ownerCount == 0 || ownerCount > MAX_OWNER_COUNT || _required > ownerCount || !(ownerCount > 1 ? _required > 1 : _required > 0)) { + revert InvalidRequirement(); + } _; } modifier ownerExists(address owner) { - require(isOwner[owner], "MultiSig: !isOwner[owner]"); + if (!isOwner[owner]) { + revert OwnerNotFound(); + } _; } @@ -87,30 +116,43 @@ contract MultiSigWallet is IMultiSigWallet { bytes memory _data, uint256 _lifetime ) { - require(_lifetime >= MINIMUM_LIFETIME || _lifetime == 0, "lifetime minimum not met"); + if (_lifetime < MINIMUM_LIFETIME && _lifetime > 0) { + revert LifetimeMinimumNotMet(); + } if (!_to.isContract()) { - require(_data.length == 0 && _value > 0, "calldata for EOA call or 0 value"); + if (_data.length > 0 || _value == 0) { + revert InsufficientValue(); + } + } + if (address(this).balance < _value) { + revert InsufficientBalance(); } - require(address(this).balance >= _value, "not enough balance"); _; } - constructor( - address[] memory _owners, - uint256 _numConfirmationsRequired, - address _governor - ) { + constructor(address[] memory _owners, uint256 _numConfirmationsRequired, address _governor) { + if (_owners.length > MAX_OWNER_COUNT) { + revert OwnersLimitReached(); + } + if (_owners.length == 0) { + revert OwnersRequired(); + } + if (_numConfirmationsRequired == 0 || _numConfirmationsRequired > _owners.length) { + revert InvalidRequirement(); + } + governor = _governor; - require(_owners.length <= MAX_OWNER_COUNT, "owners limit reached"); - require(_owners.length > 0, "owners required"); - require(_numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length, "invalid number of required confirmations"); for (uint256 i = 0; i < _owners.length; i++) { address owner = _owners[i]; - require(owner != address(0), "invalid owner"); - require(!isOwner[owner], "owner not unique"); + if (owner == address(0)) { + revert InvalidOwner(); + } + if (isOwner[owner]) { + revert OwnerNotUnique(); + } isOwner[owner] = true; owners.add(owner); @@ -124,15 +166,15 @@ contract MultiSigWallet is IMultiSigWallet { emit Deposit(msg.sender, msg.value, address(this).balance); } - function removeOwner(address owner) public override onlyWallet ownerExists(owner) { + function removeOwner(address owner) external override onlyWallet ownerExists(owner) { isOwner[owner] = false; owners.remove(owner); if (numConfirmationsRequired > owners.length()) changeRequirement(owners.length()); - + uint256 nConfirmedTxnByOwner = confirmedTransactionsByOwner[owner].length(); - - for(uint i = 0; i < nConfirmedTxnByOwner; i++){ + + for (uint i = 0; i < nConfirmedTxnByOwner; i++) { uint256 _txIndex = confirmedTransactionsByOwner[owner].at(i); Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations -= 1; @@ -141,15 +183,15 @@ contract MultiSigWallet is IMultiSigWallet { } emit OwnerRemoval(owner); } - function addOwners(address[] memory _owners) - public - override - onlyWallet - validRequirement(owners.length() + _owners.length, numConfirmationsRequired + _owners.length) - { + + function addOwners( + address[] calldata _owners + ) external override onlyWallet validRequirement(owners.length() + _owners.length, numConfirmationsRequired + _owners.length) { for (uint256 i = 0; i < _owners.length; i++) { address owner = _owners[i]; - require(owner != address(0), "MultiSig: owner address == 0"); + if (owner == address(0)) { + revert InvalidOwner(); + } _requireNewOwner(owner); isOwner[owner] = true; @@ -160,41 +202,32 @@ contract MultiSigWallet is IMultiSigWallet { changeRequirement(numConfirmationsRequired + _owners.length); } - function changeRequirement(uint256 _required) public override onlyWallet validRequirement(owners.length(), _required) { - numConfirmationsRequired = _required; - emit RequirementChange(_required); - } function submitTransaction( address _to, uint256 _value, - bytes memory _data, + bytes calldata _data, uint256 _lifetime - ) public override onlyOwnerOrGov validateSubmitTxInputs(_to, _value, _data, _lifetime) { + ) external override onlyOwnerOrGov validateSubmitTxInputs(_to, _value, _data, _lifetime) { uint256 txIndex = transactions.length; transactions.push( - Transaction({ - to: _to, - value: _value, - data: _data, - executed: false, - numConfirmations: 0, - expireTimestamp: _lifetime == 0 ? 0 : block.timestamp + _lifetime }) + Transaction({ + to: _to, + value: _value, + data: _data, + executed: false, + numConfirmations: 0, + expireTimestamp: _lifetime == 0 ? 0 : block.timestamp + _lifetime + }) ); - - whitelistedBytesCode[_to] = _to.getExtCodeHash(); + + allowlistedBytesCode[_to] = _to.getExtCodeHash(); emit SubmitTransaction(txIndex, msg.sender, _to, _value, _data); } - function confirmTransaction(uint256 _txIndex) - public - override - onlyOwnerOrGov - txExists(_txIndex) - notExecuted(_txIndex) - notConfirmed(_txIndex) - notExpired(_txIndex) - { + function confirmTransaction( + uint256 _txIndex + ) external override onlyOwnerOrGov txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) notExpired(_txIndex) { Transaction storage transaction = transactions[_txIndex]; _requireTargetCodeNotChanged(transaction.to); @@ -205,24 +238,19 @@ contract MultiSigWallet is IMultiSigWallet { emit ConfirmTransaction(msg.sender, _txIndex); } - function executeTransaction(uint256 _txIndex) - public - override - onlyOwnerOrGov - txExists(_txIndex) - notExecuted(_txIndex) - notExpired(_txIndex) - { + function executeTransaction(uint256 _txIndex) external override onlyOwnerOrGov txExists(_txIndex) notExecuted(_txIndex) notExpired(_txIndex) { Transaction storage transaction = transactions[_txIndex]; _requireTargetCodeNotChanged(transaction.to); - require(transaction.numConfirmations >= numConfirmationsRequired, "cannot execute tx"); + if (transaction.numConfirmations < numConfirmationsRequired) { + revert TxNotConfirmed(); + } transaction.executed = true; - + (bool success, bytes memory data) = transaction.to.call{ value: transaction.value }(transaction.data); - + if (success) { emit ExecuteTransaction(msg.sender, _txIndex); } else { @@ -230,54 +258,53 @@ contract MultiSigWallet is IMultiSigWallet { } } - function revokeConfirmation(uint256 _txIndex) - public - override - onlyOwnerOrGov - txExists(_txIndex) - notExecuted(_txIndex) - notExpired(_txIndex) - { + function revokeConfirmation(uint256 _txIndex) external override onlyOwnerOrGov txExists(_txIndex) notExecuted(_txIndex) notExpired(_txIndex) { Transaction storage transaction = transactions[_txIndex]; - require(confirmedTransactionsByOwner[msg.sender].contains(_txIndex), "tx not confirmed"); + if (!confirmedTransactionsByOwner[msg.sender].contains(_txIndex)) { + revert TxNotConfirmed(); + } transaction.numConfirmations -= 1; confirmedTransactionsByOwner[msg.sender].remove(_txIndex); emit RevokeConfirmation(msg.sender, _txIndex); } - function getOwners() public view override returns (address[] memory) { + function getOwners() external view override returns (address[] memory) { return owners.values(); } - function getTransactionCount() public view override returns (uint256) { + function getTransactionCount() external view override returns (uint256) { return transactions.length; } - function getTransaction(uint256 _txIndex) - public + function getTransaction( + uint256 _txIndex + ) + external view override - returns ( - address to, - uint256 value, - bytes memory data, - bool executed, - uint256 numConfirmations, - uint256 expireTimestamp - ) + returns (address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations, uint256 expireTimestamp) { Transaction memory transaction = transactions[_txIndex]; return (transaction.to, transaction.value, transaction.data, transaction.executed, transaction.numConfirmations, transaction.expireTimestamp); } + function changeRequirement(uint256 _required) public override onlyWallet validRequirement(owners.length(), _required) { + numConfirmationsRequired = _required; + emit RequirementChange(_required); + } + function _requireNewOwner(address owner) internal view { - require(!isOwner[owner], "MultiSig: Owner already exists"); + if (isOwner[owner]) { + revert OwnerAlreadyExists(); + } } function _requireTargetCodeNotChanged(address target) internal view { - require(whitelistedBytesCode[target] == target.getExtCodeHash(), "target code changed"); + if (allowlistedBytesCode[target] != target.getExtCodeHash()) { + revert TargetCodeChanged(); + } } } diff --git a/contracts/dao/treasury/interfaces/IMultiSigWallet.sol b/contracts/dao/treasury/interfaces/IMultiSigWallet.sol index 0fde3f9..94c45c7 100644 --- a/contracts/dao/treasury/interfaces/IMultiSigWallet.sol +++ b/contracts/dao/treasury/interfaces/IMultiSigWallet.sol @@ -22,12 +22,7 @@ interface IMultiSigWallet { function changeRequirement(uint256 _required) external; - function submitTransaction( - address _to, - uint256 _value, - bytes memory _data, - uint256 _expireTimestamp - ) external; + function submitTransaction(address _to, uint256 _value, bytes memory _data, uint256 _expireTimestamp) external; function confirmTransaction(uint256 _txIndex) external; @@ -39,14 +34,7 @@ interface IMultiSigWallet { function getTransactionCount() external returns (uint256); - function getTransaction(uint256 _txIndex) - external - returns ( - address to, - uint256 value, - bytes memory data, - bool executed, - uint256 numConfirmations, - uint256 expireTimestamp - ); + function getTransaction( + uint256 _txIndex + ) external returns (address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations, uint256 expireTimestamp); } diff --git a/scripts/migrations/test/3_add_vault_operator.js b/scripts/migrations/test/3_add_vault_operator.js index 93c829e..b2c45cf 100644 --- a/scripts/migrations/test/3_add_vault_operator.js +++ b/scripts/migrations/test/3_add_vault_operator.js @@ -9,7 +9,7 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint const VaultProxy = artifacts.require('./common/proxy/VaultProxy.sol') const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') -const _encodeTransferFunction = (_account, _amount) => { +const _encodeAddRewardsOpertor = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'addRewardsOperator', type: 'function', @@ -28,7 +28,7 @@ module.exports = async function(deployer) { let result = await multiSigWallet.submitTransaction( VaultProxy.address, EMPTY_BYTES, - _encodeTransferFunction(StakingProxy.address), + _encodeAddRewardsOpertor(StakingProxy.address), 0, {gas: 8000000} ); diff --git a/scripts/tests/dao/demo/staking.demo.test.js b/scripts/tests/dao/demo/staking.demo.test.js index 7a5af13..7e8697d 100644 --- a/scripts/tests/dao/demo/staking.demo.test.js +++ b/scripts/tests/dao/demo/staking.demo.test.js @@ -9,7 +9,8 @@ const blockchain = require("../../helpers/blockchain"); const maxGasForTxn = 600000 const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); const SYSTEM_ACC = accounts[0]; @@ -421,9 +422,9 @@ describe("Staking Test and Upgrade Test", () => { it("Should not unlock locked position before the end of the lock position's lock period - staker_1", async() => { - const errorMessage = "lock close"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingService.unlock(1, {from: staker_1}), errTypes.revert, errorMessage diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index dfa6056..e24b273 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -38,7 +38,8 @@ const eventsHelper = require("../helpers/eventsHelper"); const { assert } = require("chai"); const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../helpers/expectThrow'); const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -1018,9 +1019,9 @@ describe("DAO Demo", () => { it("Should Revert: early unlock not possible", async() => { await blockchain.mineBlock(await _getTimeStamp() + 20); - const errorMessage = "early infeasible"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingService.earlyUnlock(1, {from: comity_1}), errTypes.revert, errorMessage diff --git a/scripts/tests/dao/governance/multisig-treasury.test.js b/scripts/tests/dao/governance/multisig-treasury.test.js index 35956fb..5e960bf 100644 --- a/scripts/tests/dao/governance/multisig-treasury.test.js +++ b/scripts/tests/dao/governance/multisig-treasury.test.js @@ -2,7 +2,8 @@ const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); // constants @@ -119,16 +120,16 @@ describe('MultiSig Wallet', () => { }); it('Shoud revert when trying to directly remove or add an owner', async() => { - let errorMessage = "MultiSig: Only this wallet can use this funciton"; + let errorMessage = "revert"; initial_owners = await multiSigWallet.getOwners(); - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.removeOwner(initial_owners[1], {"from": accounts[1]}), errTypes.revert, errorMessage ); - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.addOwners([accounts[3]], {"from": accounts[1]}), errTypes.revert, errorMessage @@ -142,9 +143,9 @@ describe('MultiSig Wallet', () => { }); it('Shoud revert when trying to execute a transaction without enough signers', async() => { - let errorMessage = "cannot execute tx"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.executeTransaction(txIndex1, {from: accounts[0]}), errTypes.revert, errorMessage @@ -161,9 +162,9 @@ describe('MultiSig Wallet', () => { await multiSigWallet.revokeConfirmation(txIndex1, {from: accounts[1]}); - let errorMessage = "cannot execute tx"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.executeTransaction(txIndex1, {from: accounts[1]}), errTypes.revert, errorMessage @@ -190,9 +191,9 @@ describe('MultiSig Wallet', () => { await multiSigWallet.revokeConfirmation(txIndex3, {from: accounts[1]}); - let errorMessage = "cannot execute tx"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.executeTransaction(txIndex3, {from: accounts[1]}), errTypes.revert, errorMessage @@ -202,9 +203,9 @@ describe('MultiSig Wallet', () => { }); it('Fail xecute the transaction to remove third signer because min number of confirmations not reached', async() => { - let errorMessage = "cannot execute tx"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( multiSigWallet.executeTransaction(txIndex2, {from: accounts[0]}), errTypes.revert, errorMessage diff --git a/scripts/tests/dao/governance/proposal-flow.test.js b/scripts/tests/dao/governance/proposal-flow.test.js index 8368da4..67d5de5 100644 --- a/scripts/tests/dao/governance/proposal-flow.test.js +++ b/scripts/tests/dao/governance/proposal-flow.test.js @@ -3,7 +3,8 @@ const eventsHelper = require("../../helpers/eventsHelper"); const { assert } = require("chai"); const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -42,10 +43,10 @@ const _encodeEmergencyStop = () => { },[]); } -const _encodeBlacklistProposer = (_account,_blacklistStatus) =>{ +const _encodeBlocklistProposer = (_account,_blocklistStatus) =>{ return web3.eth.abi.encodeFunctionCall( { - name: 'setBlacklistStatusForProposer', + name: 'setBlocklistStatusForProposer', type: 'function', inputs: [ { @@ -54,10 +55,10 @@ const _encodeBlacklistProposer = (_account,_blacklistStatus) =>{ }, { type: 'bool', - name: 'blacklistStatus' + name: 'blocklistStatus' } ] - },[_account,_blacklistStatus]); + },[_account,_blocklistStatus]); } const T_TO_STAKE = web3.utils.toWei('50000', 'ether'); @@ -267,11 +268,11 @@ describe('Proposal flow', () => { }); - it('Should revert transfer if holder is not whitelisted to transfer', async() => { + it('Should revert transfer if holder is not allowlisted to transfer', async() => { - let errorMessage = "VMainToken: is intransferable unless the sender is whitelisted"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( vMainToken.transfer( accounts[2], "10", @@ -286,10 +287,9 @@ describe('Proposal flow', () => { describe("Update Parameter Through Governer", async() => { it('Should revert proposal if: proposer votes below proposal threshold', async() => { + let errorMessage = "revert"; - let errorMessage = "Governor: proposer votes below threshold"; - - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [box.address], [0], @@ -416,9 +416,9 @@ describe('Proposal flow', () => { {"from": accounts[0]} ); expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal("5"); - const errorMessage = "TimelockController: operation is not ready"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.execute( [box.address], [0], @@ -444,9 +444,9 @@ describe('Proposal flow', () => { await blockchain.mineBlock(timestamp + nextBlock); nextBlock++; } - const errorMessage = "TimelockController: operation is not ready"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.execute( [box.address], [0], @@ -536,9 +536,9 @@ describe('Proposal flow', () => { it('Should revert on creating another proposal too soon', async() => { await blockchain.mineBlock(await _getTimeStamp() + 1); - const errorMessage = 'Can submit only after certain delay' + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [multiSigWallet.address], [0], @@ -584,9 +584,9 @@ describe('Proposal flow', () => { }); it("Should not allow an account to vote twice on the same proposal", async () => { - const errorMessage = "GovernorVotingSimple: vote already cast"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.castVote(proposalId2, "1", {"from": STAKER_1}), errTypes.revert, errorMessage @@ -595,9 +595,9 @@ describe('Proposal flow', () => { }); it("Should not vote outside of option range", async () => { - const errorMessage = "GovernorVotingSimple: invalid value for enum VoteType"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.castVote(proposalId2, "3", {"from": STAKER_2}), errTypes.revert, errorMessage @@ -621,9 +621,9 @@ describe('Proposal flow', () => { it("Should not accept votes outside of the voting period", async () => { - const errorMessage = "Governor: vote inactive"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.castVote(proposalId2, "1", {"from": STAKER_1}), errTypes.revert, errorMessage @@ -1209,12 +1209,12 @@ describe('Proposal flow', () => { }, [streamReward1.address]); // create a proposal in MainToken governor - let errorMessage = "Governor: proposer votes below threshold"; + let errorMessage = "revert"; await FTHMToken.approve(stakingService.address,T_TO_STAKE, {from: NOT_STAKER}) await _transferFromMultiSigTreasury(NOT_STAKER) //get 1VOTE Token only. await stakingService.createLock(web3.utils.toWei('999','ether'),lockingPeriod,{from: NOT_STAKER}); - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [stakingService.address], [0], @@ -1339,12 +1339,12 @@ describe('Proposal flow', () => { }); - it('Should blacklist a proposer', async() =>{ - const _blacklistAProposer = async(account, blacklistStatus) => { + it('Should blocklist a proposer', async() =>{ + const _blocklistAProposer = async(account, blocklistStatus) => { const result = await multiSigWallet.submitTransaction( mainTokenGovernor.address, EMPTY_BYTES, - _encodeBlacklistProposer(account, blacklistStatus), + _encodeBlocklistProposer(account, blocklistStatus), 0, {"from": accounts[0]} ) @@ -1355,12 +1355,12 @@ describe('Proposal flow', () => { await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); } - await _blacklistAProposer(accounts[5], true) + await _blocklistAProposer(accounts[5], true) }) - it('Should revert on propose by blacklisted msg.sender', async() =>{ - let errorMessage = "Proposer is blacklisted"; - await shouldRevert( + it('Should revert on propose by blocklisted msg.sender', async() =>{ + let errorMessage = "revert"; + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [box.address], [0], @@ -1397,9 +1397,9 @@ describe('Proposal flow', () => { }) it('Should fail to propose on emergency stop', async() =>{ - let errorMessage = "not live"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [box.address], [0], @@ -1413,8 +1413,8 @@ describe('Proposal flow', () => { }) it('Should fail to execute on emergency stop', async() =>{ - let errorMessage = "not live"; - await shouldRevert( + let errorMessage = "revert"; + await shouldRevertAndHaveSubstring( mainTokenGovernor.execute( [mainTokenGovernor.address], [0], diff --git a/scripts/tests/dao/governance/token-creation-though-gov.test.js b/scripts/tests/dao/governance/token-creation-though-gov.test.js index 459dc85..972ef33 100644 --- a/scripts/tests/dao/governance/token-creation-though-gov.test.js +++ b/scripts/tests/dao/governance/token-creation-though-gov.test.js @@ -2,7 +2,8 @@ const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -189,11 +190,11 @@ describe('Token Creation Through Governance', () => { }); - it('Should revert transfer if holder is not whitelisted to transfer', async() => { + it('Should revert transfer if holder is not allowlisted to transfer', async() => { - let errorMessage = "VMainToken: is intransferable unless the sender is whitelisted"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( vMainToken.transfer( accounts[2], "10", @@ -209,9 +210,9 @@ describe('Token Creation Through Governance', () => { it('Should revert proposal if: proposer votes below proposal threshold', async() => { - let errorMessage = "Governor: proposer votes below threshold"; + let errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( mainTokenGovernor.propose( [erc20Factory.address], [0], diff --git a/scripts/tests/dao/staking/staking.test.js b/scripts/tests/dao/staking/staking.test.js index 331e54d..482af53 100644 --- a/scripts/tests/dao/staking/staking.test.js +++ b/scripts/tests/dao/staking/staking.test.js @@ -9,7 +9,8 @@ const blockchain = require("../../helpers/blockchain"); const maxGasForTxn = 600000 const { shouldRevert, - errTypes + errTypes, + shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); const SYSTEM_ACC = accounts[0]; @@ -452,9 +453,9 @@ describe("Staking Test", () => { it("Should not unlock locked position before the end of the lock possition's lock period - staker_1", async() => { - const errorMessage = "lock close"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingService.unlock(1, {from: staker_1}), errTypes.revert, errorMessage @@ -488,9 +489,9 @@ describe("Staking Test", () => { await stakingService.claimAllStreamRewardsForLock(1, {from: staker_1}) await blockchain.mineBlock(10 + await _getTimeStamp()) await stakingService.unlock(1, {from : staker_1}); - const errorMessage = "out of index"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingGetterService.getLockInfo(staker_1,3), errTypes.revert, errorMessage @@ -514,9 +515,9 @@ describe("Staking Test", () => { const differenceInBalance = _calculateRemainingBalance(afterVOTEBalance,beforeVOTEBalance) amountOfVFTHMLock3.should.be.bignumber.equal(differenceInBalance.toString()) - const errorMessage = "out of index"; + const errorMessage = "revert"; // The last lock possition should no longer be accesible - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingGetterService.getLockInfo(staker_2,1), errTypes.revert, errorMessage @@ -528,9 +529,9 @@ describe("Staking Test", () => { await stakingService.claimAllStreamRewardsForLock(1, {from: staker_3}) await blockchain.mineBlock(10 + await _getTimeStamp()) await stakingService.unlock(1, {from: staker_3}); - const errorMessage = "out of index"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingGetterService.getLockInfo(staker_3,1), errTypes.revert, errorMessage @@ -543,9 +544,9 @@ describe("Staking Test", () => { await stakingService.claimAllStreamRewardsForLock(1, {from: staker_4}) await blockchain.mineBlock(10 + await _getTimeStamp()) await stakingService.unlock(1, {from: staker_4}); - const errorMessage = "out of index"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingGetterService.getLockInfo(staker_4,1), errTypes.revert, errorMessage @@ -1120,9 +1121,9 @@ describe("Staking Test", () => { pendingStakedFTHM = await stakingService.getUsersPendingRewards(staker_3,streamId) console.log("Pending user accounts with early withdrawal: ",_convertToEtherBalance(pendingStakedFTHM.toString())) - const errorMessage = "out of index"; + const errorMessage = "revert"; - await shouldRevert( + await shouldRevertAndHaveSubstring( stakingGetterService.getLockInfo(staker_3,lockId), errTypes.revert, errorMessage @@ -1196,8 +1197,8 @@ describe("Staking Test", () => { it('Should not make lock position with 0 lock period', async() => { const unlockTime = 0; await blockchain.mineBlock(await _getTimeStamp() + 100); - const errorMessage = "min lock" - await shouldRevert( + const errorMessage = "revert" + await shouldRevertAndHaveSubstring( stakingService.createLock(sumToDeposit,unlockTime,{from: staker_4, gas: maxGasForTxn}), errTypes.revert, errorMessage diff --git a/scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js similarity index 67% rename from scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js rename to scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js index a5720f1..742918e 100644 --- a/scripts/units/stablecoin/book-keeper/blacklist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js @@ -4,16 +4,16 @@ const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.j const addressesExternal = JSON.parse(rawdataExternal); const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS -const TO_BE_BLACKLISTED = "0x" -const _encodeBlacklist = (toBeBlacklistedAddress) => { +const TO_BE_ALLOWLISTED = "0x" +const _encodeAllowlist = (toBeAllowlistedAddress) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name: 'blacklist', + name: 'allowlist', type: 'function', inputs: [{ type: 'address', - name: 'toBeBlacklistedAddress' + name: 'toBeAllowlistedAddress' }] - }, [toBeBlacklistedAddress]); + }, [toBeAllowlistedAddress]); return toRet; } @@ -22,8 +22,8 @@ const _encodeBlacklist = (toBeBlacklistedAddress) => { module.exports = async function(deployer) { await txnHelper.submitAndExecute( - _encodeBlacklist(TO_BE_BLACKLISTED), + _encodeAllowlist(TO_BE_ALLOWLISTED), BOOK_KEEPER_ADDRESS, - "setBlacklistedAddressBookkeeper" + "setAllowlistedAddressBookkeeper" ) } \ No newline at end of file diff --git a/scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js similarity index 67% rename from scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js rename to scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js index e0c1748..d599314 100644 --- a/scripts/units/stablecoin/book-keeper/whitelist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js @@ -4,16 +4,16 @@ const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.j const addressesExternal = JSON.parse(rawdataExternal); const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS -const TO_BE_WHITELISTED = "0x" -const _encodeWhitelist = (toBeWhitelistedAddress) => { +const TO_BE_BLOCKLISTED = "0x" +const _encodeBlocklist = (toBeBlocklistedAddress) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name: 'whitelist', + name: 'blocklist', type: 'function', inputs: [{ type: 'address', - name: 'toBeWhitelistedAddress' + name: 'toBeBlocklistedAddress' }] - }, [toBeWhitelistedAddress]); + }, [toBeBlocklistedAddress]); return toRet; } @@ -22,8 +22,8 @@ const _encodeWhitelist = (toBeWhitelistedAddress) => { module.exports = async function(deployer) { await txnHelper.submitAndExecute( - _encodeWhitelist(TO_BE_WHITELISTED), + _encodeBlocklist(TO_BE_BLOCKLISTED), BOOK_KEEPER_ADDRESS, - "setWhitelistedAddressBookkeeper" + "setBlocklistedAddressBookkeeper" ) } \ No newline at end of file diff --git a/truffle-config.js b/truffle-config.js new file mode 100644 index 0000000..ed8e023 --- /dev/null +++ b/truffle-config.js @@ -0,0 +1,22 @@ +module.exports = { + networks: { + development: { + host: "127.0.0.1", + port: 8545, + network_id: "*" // Match any network id + } + }, + compilers: { + solc: { + version: "0.8.16", + osettings: { + optimizer: { + enabled: true, + details: { yul: false }, + runs: 150, + }, + evmVersion: 'istanbul', + }, + } + } +}; \ No newline at end of file From a7ad36bcbfea3ffbf6e03291294a5b75b413dff7 Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Mon, 27 Mar 2023 03:08:25 +0400 Subject: [PATCH 36/56] Minor changes --- contracts/dao/governance/Governor.sol | 3 ++- contracts/dao/governance/extensions/GovernorSettings.sol | 2 +- .../dao/governance/extensions/GovernorTimelockControl.sol | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index bc491be..91ac124 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -78,6 +78,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { error ProposalDelayNotPassed(); error ProposalExpired(); error ProposalNotConfirmed(); + error UnknownProposal(); modifier onlyGovernance() { if (_msgSender() != _executor()) { @@ -431,7 +432,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { uint256 snapshot = proposalSnapshot(proposalId); if (snapshot == 0) { - revert("unknown proposal id"); + revert UnknownProposal(); } if (snapshot >= block.number) { diff --git a/contracts/dao/governance/extensions/GovernorSettings.sol b/contracts/dao/governance/extensions/GovernorSettings.sol index be1a1d9..7a09763 100644 --- a/contracts/dao/governance/extensions/GovernorSettings.sol +++ b/contracts/dao/governance/extensions/GovernorSettings.sol @@ -71,10 +71,10 @@ abstract contract GovernorSettings is Governor { } function _setProposalThreshold(uint256 newProposalThreshold) internal virtual { - emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); if (newProposalThreshold == 0) { revert ZeroThreshold(); } + emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); _proposalThreshold = newProposalThreshold; } } diff --git a/contracts/dao/governance/extensions/GovernorTimelockControl.sol b/contracts/dao/governance/extensions/GovernorTimelockControl.sol index 65ea4ec..0a9abad 100644 --- a/contracts/dao/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/dao/governance/extensions/GovernorTimelockControl.sol @@ -80,7 +80,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes32 queueid = _timelockIds[proposalId]; if (queueid == bytes32(0)) { return status; - } else if (isProposalExecuted[proposalId] == true) { + } else if (isProposalExecuted[proposalId]) { return ProposalState.Executed; } else if (_timelock.isOperationPending(queueid)) { return ProposalState.Queued; From f1dec35000bcb6cc77badb687abbfdf281ce6ba8 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 27 Mar 2023 14:13:40 +0545 Subject: [PATCH 37/56] created new tests for vote tokens --- ...js => token-creation-and-delegate.test.js} | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) rename scripts/tests/dao/governance/{token-creation-though-gov.test.js => token-creation-and-delegate.test.js} (80%) diff --git a/scripts/tests/dao/governance/token-creation-though-gov.test.js b/scripts/tests/dao/governance/token-creation-and-delegate.test.js similarity index 80% rename from scripts/tests/dao/governance/token-creation-though-gov.test.js rename to scripts/tests/dao/governance/token-creation-and-delegate.test.js index 972ef33..0bc5713 100644 --- a/scripts/tests/dao/governance/token-creation-though-gov.test.js +++ b/scripts/tests/dao/governance/token-creation-and-delegate.test.js @@ -1,3 +1,4 @@ +const { web3 } = require("@openzeppelin/test-helpers/src/setup"); const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); const { @@ -31,7 +32,23 @@ const _encodeConfirmation = async (_proposalId) => { const T_TO_STAKE = web3.utils.toWei('2000', 'ether'); const STAKER_1 = accounts[5]; const STAKER_2 = accounts[6]; +const DELEGATOR_1 = accounts[3]; +const DELEGATOR_2 = accounts[4]; +const DELEGATEE_1 = accounts[7] +const DELEGATEE_2 = accounts[8] +const TO_MINT_AND_DELEGATE = web3.utils.toWei('10000', 'ether') +const _encodeGrantMinterRole=(_account) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'grantMinterRole', + type: 'function', + inputs: [{ + type: 'address', + name: '_minter' + }] + }, [_account]); + return toRet; +} const _encodeTransferFunction = (_account) => { // encoded transfer function call for the main token. @@ -372,5 +389,69 @@ describe('Token Creation Through Governance', () => { expect((await mainTokenGovernor.state(proposalId)).toString()).to.equal("7"); }) }); + + describe("#ERC20Votes-mint-delegate-burn-check", async() => { + it('Grant Minter Role to deployer', async() =>{ + const _grantMinterRole = async( + _account + ) => { + const result = await multiSigWallet.submitTransaction( + vMainToken.address, + EMPTY_BYTES, + _encodeGrantMinterRole( + _account + ), + 0, + {"from": accounts[0]} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + await _grantMinterRole(accounts[0]) + }) + + it('Mint Tokens to accounts Staker 1 and staker 2', async() => { + await vMainToken.mint(DELEGATOR_1,TO_MINT_AND_DELEGATE) + await vMainToken.mint(DELEGATOR_2,TO_MINT_AND_DELEGATE) + }) + + it('Should revert transfer if holder is not allowlisted to transfer', async() => { + + let errorMessage = "revert"; + + await shouldRevertAndHaveSubstring( + vMainToken.transfer( + accounts[2], + "1", + {from: DELEGATOR_1} + ), + errTypes.revert, + errorMessage + ); + }); + + it('Delegate Vote Tokens to accounts Staker 1 and staker 2', async() => { + await vMainToken.delegate(DELEGATEE_1, {from: DELEGATOR_1}) + await vMainToken.delegate(DELEGATEE_2, {from: DELEGATOR_2}) + expect((await vMainToken.getVotes(DELEGATEE_1)).toString()).to.equal(TO_MINT_AND_DELEGATE) + expect((await vMainToken.getVotes(DELEGATEE_2)).toString()).to.equal(TO_MINT_AND_DELEGATE) + }) + + it('Burn Vote Tokens of Delegatee', async() => { + const ZERO_TOKEN_AMOUNT = web3.utils.toWei('0','ether') + await vMainToken.burn(DELEGATOR_1, TO_MINT_AND_DELEGATE) + await vMainToken.burn(DELEGATOR_2, TO_MINT_AND_DELEGATE) + expect((await vMainToken.getVotes(DELEGATEE_1)).toString()).to.equal(ZERO_TOKEN_AMOUNT) + expect((await vMainToken.getVotes(DELEGATEE_2)).toString()).to.equal(ZERO_TOKEN_AMOUNT) + expect((await vMainToken.getVotes(DELEGATOR_1)).toString()).to.equal(ZERO_TOKEN_AMOUNT) + expect((await vMainToken.getVotes(DELEGATOR_2)).toString()).to.equal(ZERO_TOKEN_AMOUNT) + }) + + }) }); From 3349c7d441df93750ee6695635b96c85d8845e71 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 27 Mar 2023 17:21:13 +0545 Subject: [PATCH 38/56] updating logic to transfer rewards to treasury --- .solhint.json | 2 +- contracts/common/SafeERC20Staking.sol | 3 ++ contracts/dao/governance/Governor.sol | 5 ++-- contracts/dao/staking/StakingStorage.sol | 2 ++ contracts/dao/staking/StakingStructs.sol | 1 + .../staking/interfaces/IRewardsHandler.sol | 1 + .../staking/interfaces/IStakingHandler.sol | 2 ++ .../staking/packages/RewardsCalculator.sol | 7 ++++- .../dao/staking/packages/RewardsInternals.sol | 2 ++ .../dao/staking/packages/StakingHandler.sol | 29 ++++++++++++------- scripts/tests/dao/demo/staking.demo.test.js | 11 ++++++- scripts/tests/dao/full-flow-demo.test.js | 10 +++++++ scripts/tests/dao/staking/staking.test.js | 14 ++++++++- scripts/units/DEX/set-fee-to-setter.js | 2 +- scripts/units/propose_stream.js | 9 +++++- 15 files changed, 81 insertions(+), 19 deletions(-) diff --git a/.solhint.json b/.solhint.json index f33f8c0..4ab14d9 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,7 +3,7 @@ "plugins": [], "rules": { "compiler-version": ["error","0.8.16"], - "max-line-length": ["error", 150], + "max-line-length": ["error", 155], "reason-string": ["error", {"maxLength": 96}], "func-visibility": ["error", {"ignoreConstructors": true}], "not-rely-on-time": "error", diff --git a/contracts/common/SafeERC20Staking.sol b/contracts/common/SafeERC20Staking.sol index c68c891..6e502be 100644 --- a/contracts/common/SafeERC20Staking.sol +++ b/contracts/common/SafeERC20Staking.sol @@ -19,6 +19,9 @@ import "./Address.sol"; library SafeERC20Staking { using Address for address; + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index 91ac124..1adf31a 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -44,11 +44,10 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { event ConfirmProposal(address indexed signer, uint256 indexed proposalId); event RevokeConfirmation(address indexed signer, uint256 indexed proposalId); - event ExecuteProposal(address indexed signer, uint256 indexed proposalId); + event ExecuteProposal(address indexed owner, bool indexed success, bytes data); event MultiSigUpdated(address newMultiSig, address oldMultiSig); event MaxTargetUpdated(uint256 newMaxTargets, uint256 oldMaxTargets); event ProposalTimeDelayUpdated(uint256 newProposalTimeDelay, uint256 oldProposalTimeDelay); - event ExecuteTransaction(address indexed owner, bool indexed success, bytes data); event ProposalLifetimeUpdated(uint256 newProposalLifetime, uint256 oldProposalLifetime); event EmergencyStop(); @@ -514,7 +513,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { ) internal virtual { for (uint256 i = 0; i < targets.length; ++i) { (bool success, bytes memory returndata) = targets[i].call{ value: values[i] }(calldatas[i]); - emit ExecuteTransaction(msg.sender, success, returndata); + emit ExecuteProposal(msg.sender, success, returndata); } } diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index bb86268..05f0fd9 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -44,6 +44,7 @@ contract StakingStorage { address public vault; address public rewardsCalculator; bool internal councilsInitialized; + address public treasury; bool internal mainStreamInitialized; ///Weighting coefficient for shares and penalties @@ -55,4 +56,5 @@ contract StakingStorage { ///Mapping (user => LockedBalance) to keep locking information for each user mapping(address => LockedBalance[]) internal locks; mapping(uint256 => uint256) public streamTotalUserPendings; + } diff --git a/contracts/dao/staking/StakingStructs.sol b/contracts/dao/staking/StakingStructs.sol index 14393c4..942149b 100644 --- a/contracts/dao/staking/StakingStructs.sol +++ b/contracts/dao/staking/StakingStructs.sol @@ -54,6 +54,7 @@ struct LockedBalance { struct Stream { address owner; // stream owned by the ERC-20 reward token owner address manager; // stream manager handled by Main stream manager role + uint256 percentToTreasury; address rewardToken; StreamStatus status; uint256 rewardDepositAmount; // the reward amount that has been deposited by third party diff --git a/contracts/dao/staking/interfaces/IRewardsHandler.sol b/contracts/dao/staking/interfaces/IRewardsHandler.sol index 80c34bc..7424aed 100644 --- a/contracts/dao/staking/interfaces/IRewardsHandler.sol +++ b/contracts/dao/staking/interfaces/IRewardsHandler.sol @@ -9,6 +9,7 @@ interface IRewardsHandler { function validateStreamParameters( address streamOwner, address rewardToken, + uint256 percentToTreasury, uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] calldata scheduleTimes, diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index 782fbb9..9f8de03 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -24,6 +24,7 @@ interface IStakingHandler { function proposeStream( address streamOwner, address rewardToken, + uint256 percentToTreasury, uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] calldata scheduleTimes, @@ -66,4 +67,5 @@ interface IStakingHandler { function setMinimumLockPeriod(uint256 _minLockPeriod) external; function setMaxLockPositions(uint256 newMaxLockPositions) external; + function setTreasuryAddress(address newTreasury) external; } diff --git a/contracts/dao/staking/packages/RewardsCalculator.sol b/contracts/dao/staking/packages/RewardsCalculator.sol index 63de490..16b9948 100644 --- a/contracts/dao/staking/packages/RewardsCalculator.sol +++ b/contracts/dao/staking/packages/RewardsCalculator.sol @@ -8,6 +8,7 @@ import "../../../common/math/FullMath.sol"; // solhint-disable not-rely-on-time contract RewardsCalculator is IRewardsHandler { + uint256 public constant MAXIMUM_PERCENT_TO_TREASURY = 5000; //10000th error BadOwnerError(); error BadRewardTokenError(); error NoMinDepositError(); @@ -26,11 +27,13 @@ contract RewardsCalculator is IRewardsHandler { error QueryBeforeStartError(); error QueryAfterEndError(); error InvalidIndexError(); + error BadPercentToTreasuryError(); // solhint-disable code-complexity function validateStreamParameters( address streamOwner, address rewardToken, + uint256 percentToTreasury, uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] calldata scheduleTimes, @@ -52,7 +55,6 @@ contract RewardsCalculator is IRewardsHandler { if (maxDepositAmount != scheduleRewards[0]) { revert InvalidMaxDepositError(); } - // scheduleTimes[0] == proposal expiration time if (scheduleTimes[0] <= block.timestamp) { revert BadExpirationError(); } @@ -65,6 +67,9 @@ contract RewardsCalculator is IRewardsHandler { if (tau == 0) { revert BadTauError(); } + if (percentToTreasury > MAXIMUM_PERCENT_TO_TREASURY){ + revert BadPercentToTreasuryError(); + } for (uint256 i = 1; i < scheduleTimes.length; i++) { if (scheduleTimes[i] <= scheduleTimes[i - 1]) { revert BadTimesError(); diff --git a/contracts/dao/staking/packages/RewardsInternals.sol b/contracts/dao/staking/packages/RewardsInternals.sol index 4c45c5d..36bfd29 100644 --- a/contracts/dao/staking/packages/RewardsInternals.sol +++ b/contracts/dao/staking/packages/RewardsInternals.sol @@ -86,6 +86,7 @@ contract RewardsInternals is StakingStorage, IStakingEvents { function _validateStreamParameters( address streamOwner, address rewardToken, + uint256 percentToTreasury, uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] memory scheduleTimes, @@ -95,6 +96,7 @@ contract RewardsInternals is StakingStorage, IStakingEvents { IRewardsHandler(rewardsCalculator).validateStreamParameters( streamOwner, rewardToken, + percentToTreasury, maxDepositAmount, minDepositAmount, scheduleTimes, diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index d323027..17244d9 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -14,7 +14,6 @@ import "../../../common/SafeERC20Staking.sol"; contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, AdminPausable { using SafeERC20Staking for IERC20; bytes32 public constant STREAM_MANAGER_ROLE = keccak256("STREAM_MANAGER_ROLE"); - bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); error NotPaused(); error VaultNotSupported(address _vault); @@ -80,9 +79,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } pausableInit(1, _admin); _grantRole(STREAM_MANAGER_ROLE, _admin); - _grantRole(TREASURY_ROLE, _admin); maxLockPeriod = ONE_YEAR; minLockPeriod = _minLockPeriod; + treasury = _admin; } /** @@ -99,13 +98,14 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A revert AlreadyInitialized(); } IERC20(mainToken).safeTransferFrom(msg.sender, address(this), scheduleRewards[0]); - _validateStreamParameters(_owner, mainToken, scheduleRewards[MAIN_STREAM], scheduleRewards[MAIN_STREAM], scheduleTimes, scheduleRewards, tau); + _validateStreamParameters(_owner, mainToken, 0,scheduleRewards[MAIN_STREAM], scheduleRewards[MAIN_STREAM], scheduleTimes, scheduleRewards, tau); uint256 streamId = 0; Schedule memory schedule = Schedule(scheduleTimes, scheduleRewards); streams.push( Stream({ owner: _owner, manager: _owner, + percentToTreasury:0, rewardToken: mainToken, maxDepositAmount: 0, minDepositAmount: 0, @@ -144,13 +144,14 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A function proposeStream( address streamOwner, address rewardToken, + uint256 percentToTreasury, //10000th uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] calldata scheduleTimes, uint256[] calldata scheduleRewards, uint256 tau ) external override onlyRole(STREAM_MANAGER_ROLE) { - _validateStreamParameters(streamOwner, rewardToken, maxDepositAmount, minDepositAmount, scheduleTimes, scheduleRewards, tau); + _validateStreamParameters(streamOwner, rewardToken, percentToTreasury, maxDepositAmount, minDepositAmount, scheduleTimes, scheduleRewards, tau); if (!IVault(vault).isSupportedToken(rewardToken)) { revert UnsupportedToken(); } @@ -160,6 +161,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A Stream({ owner: streamOwner, manager: msg.sender, + percentToTreasury: percentToTreasury, rewardToken: rewardToken, maxDepositAmount: maxDepositAmount, minDepositAmount: minDepositAmount, @@ -185,17 +187,17 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); stream.status = StreamStatus.ACTIVE; + uint256 updatedRewardTokenAmount = rewardTokenAmount - stream.percentToTreasury * rewardTokenAmount / 10000; + stream.rewardDepositAmount = updatedRewardTokenAmount; + _updateStreamsRewardsSchedules(streamId, updatedRewardTokenAmount); - stream.rewardDepositAmount = rewardTokenAmount; - if (rewardTokenAmount < stream.maxDepositAmount) { - _updateStreamsRewardsSchedules(streamId, rewardTokenAmount); - } if (stream.schedule.reward[0] != stream.rewardDepositAmount) { revert BadStart(); } emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); - _transfer(rewardTokenAmount, stream.rewardToken); + IERC20(stream.rewardToken).safeTransfer(treasury, rewardTokenAmount - updatedRewardTokenAmount); + _transfer(updatedRewardTokenAmount, stream.rewardToken); } /** @@ -383,7 +385,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Penalty accrued due to early unlocking can be withdrawn to some address, most likely the treasury. * Address with TREASURY_ROLE can access this function, which is Multisig at time of deployment */ - function withdrawPenalty(address penaltyReceiver) external override pausable(1) onlyRole(TREASURY_ROLE) { + function withdrawPenalty(address penaltyReceiver) external override pausable(1) onlyRole(DEFAULT_ADMIN_ROLE) { if (totalPenaltyBalance == 0) { revert ZeroPenalty(); } @@ -412,6 +414,13 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A maxLockPositions = newMaxLockPositions; } + function setTreasuryAddress(address newTreasury) external override onlyRole(DEFAULT_ADMIN_ROLE) { + if (newTreasury == address(0)){ + revert ZeroAddress(); + } + treasury = newTreasury; + } + function _createLock(uint256 amount, uint256 lockPeriod, address account) internal { if (lockPeriod < minLockPeriod) { revert MinLockPeriodNotMet(); diff --git a/scripts/tests/dao/demo/staking.demo.test.js b/scripts/tests/dao/demo/staking.demo.test.js index 7e8697d..40baf0a 100644 --- a/scripts/tests/dao/demo/staking.demo.test.js +++ b/scripts/tests/dao/demo/staking.demo.test.js @@ -23,7 +23,7 @@ const staker_5 = accounts[7]; const stream_manager = accounts[7]; const stream_rewarder_1 = accounts[8]; const stream_rewarder_2 = accounts[9]; - +const percentToTreasury = 50; const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; // event const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; @@ -123,6 +123,7 @@ const _encodeAddSupportedTokenFunction = (_token) => { const _encodeProposeStreamFunction = ( _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -138,6 +139,10 @@ const _encodeProposeStreamFunction = ( },{ type: 'address', name: 'rewardToken' + }, + ,{ + type: 'uint256', + name: 'percentToTreasury' },{ type: 'uint256', name: 'maxDepositAmount' @@ -157,6 +162,7 @@ const _encodeProposeStreamFunction = ( }, [ _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -560,6 +566,7 @@ describe("Staking Test and Upgrade Test", () => { const _proposeStreamFromMultiSigTreasury = async ( _stream_rewarder_2, _streamReward2Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -572,6 +579,7 @@ describe("Staking Test and Upgrade Test", () => { _encodeProposeStreamFunction( _stream_rewarder_2, _streamReward2Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -592,6 +600,7 @@ describe("Staking Test and Upgrade Test", () => { await _proposeStreamFromMultiSigTreasury( stream_rewarder_2, streamReward2Address, + percentToTreasury, maxRewardProposalAmountForAStream, minRewardProposalAmountForAStream, scheduleTimes, diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index e24b273..c66f045 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -69,6 +69,7 @@ const stream_rewarder_1 = accounts[5]; let vault_test_address; let treasury; +const percentToTreasury = 50 const _encodeWithdrawPenaltyFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -200,6 +201,7 @@ const _encodeAddSupportedTokenFunction = (_token) => { const _encodeProposeStreamFunction = ( _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -215,6 +217,10 @@ const _encodeProposeStreamFunction = ( },{ type: 'address', name: 'rewardToken' + }, + ,{ + type: 'uint256', + name: 'percentToTreasury' },{ type: 'uint256', name: 'maxDepositAmount' @@ -234,6 +240,7 @@ const _encodeProposeStreamFunction = ( }, [ _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -488,6 +495,7 @@ describe("DAO Demo", () => { const _proposeStreamFromMultiSigTreasury = async ( _stream_rewarder_1, _streamReward1Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -500,6 +508,7 @@ describe("DAO Demo", () => { _encodeProposeStreamFunction( _stream_rewarder_1, _streamReward1Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -519,6 +528,7 @@ describe("DAO Demo", () => { await _proposeStreamFromMultiSigTreasury( stream_rewarder_1, streamReward1Address, + percentToTreasury, maxRewardProposalAmountForAStream, minRewardProposalAmountForAStream, scheduleTimes, diff --git a/scripts/tests/dao/staking/staking.test.js b/scripts/tests/dao/staking/staking.test.js index 482af53..7de1aca 100644 --- a/scripts/tests/dao/staking/staking.test.js +++ b/scripts/tests/dao/staking/staking.test.js @@ -26,7 +26,7 @@ const stream_rewarder_1 = accounts[8]; const stream_rewarder_2 = accounts[9]; let vault_test_address; - +const percentToTreasury = 50 const _createVoteWeights = ( voteShareCoef, @@ -96,6 +96,7 @@ const _convertToEtherBalance = (balance) => { const _encodeProposeStreamFunction = ( _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -111,6 +112,10 @@ const _encodeProposeStreamFunction = ( },{ type: 'address', name: 'rewardToken' + }, + ,{ + type: 'uint256', + name: 'percentToTreasury' },{ type: 'uint256', name: 'maxDepositAmount' @@ -130,6 +135,7 @@ const _encodeProposeStreamFunction = ( }, [ _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -616,6 +622,7 @@ describe("Staking Test", () => { const _proposeStreamFromMultiSigTreasury = async ( _stream_rewarder_1, _streamReward1Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -628,6 +635,7 @@ describe("Staking Test", () => { _encodeProposeStreamFunction( _stream_rewarder_1, _streamReward1Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -647,6 +655,7 @@ describe("Staking Test", () => { await _proposeStreamFromMultiSigTreasury( stream_rewarder_1, streamReward1Address, + percentToTreasury, maxRewardProposalAmountForAStream, minRewardProposalAmountForAStream, scheduleTimes, @@ -685,6 +694,7 @@ describe("Staking Test", () => { const _proposeStreamFromMultiSigTreasury = async ( _stream_rewarder_2, _streamReward2Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -697,6 +707,7 @@ describe("Staking Test", () => { _encodeProposeStreamFunction( _stream_rewarder_2, _streamReward2Address, + _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, _scheduleTimes, @@ -716,6 +727,7 @@ describe("Staking Test", () => { await _proposeStreamFromMultiSigTreasury( stream_rewarder_2, streamReward2Address, + percentToTreasury, maxRewardProposalAmountForAStream, minRewardProposalAmountForAStream, scheduleTimes, diff --git a/scripts/units/DEX/set-fee-to-setter.js b/scripts/units/DEX/set-fee-to-setter.js index b70ab55..8b7dfc1 100644 --- a/scripts/units/DEX/set-fee-to-setter.js +++ b/scripts/units/DEX/set-fee-to-setter.js @@ -20,7 +20,7 @@ const _encodeSetFeeToSetter = (_feeToSetter) => { module.exports = async function(deployer) { - + await txnHelper.submitAndExecute( _encodeSetFeeToSetter(FEE_TO), FEE_TO_SETTER, diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 8497009..76d4701 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -12,11 +12,12 @@ const REWARD_TOKEN_ADDRESS = "" const STREAM_OWNER = "" const MAX_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') const MIN_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') - +const PERCENT_TO_TREASURY = 0 const _encodeProposeStreamFunction = ( _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -32,6 +33,10 @@ const _encodeProposeStreamFunction = ( },{ type: 'address', name: 'rewardToken' + }, + ,{ + type: 'uint256', + name: 'percentToTreasury' },{ type: 'uint256', name: 'maxDepositAmount' @@ -51,6 +56,7 @@ const _encodeProposeStreamFunction = ( }, [ _owner, _rewardToken, + _percentToTreasury, _maxDepositedAmount, _minDepositedAmount, _scheduleTimes, @@ -90,6 +96,7 @@ module.exports = async function(deployer) { _encodeProposeStreamFunction( STREAM_OWNER, REWARD_TOKEN_ADDRESS, + PERCENT_TO_TREASURY, MAX_DEPOSIT_AMOUNT, MIN_DEPOSIT_AMOUNT, scheduleTimes, From 509d9fd70bcaecc7f587758baa7117bea1fb69f5 Mon Sep 17 00:00:00 2001 From: ssubik Date: Mon, 27 Mar 2023 18:30:02 +0545 Subject: [PATCH 39/56] rewards to treasury --- contracts/dao/staking/StakingStorage.sol | 1 + contracts/dao/staking/packages/StakingHandler.sol | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index 05f0fd9..2dea31d 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -14,6 +14,7 @@ contract StakingStorage { uint32 internal constant ONE_MONTH = 2629746; uint32 internal constant ONE_YEAR = 31536000; uint32 internal constant ONE_DAY = 86400; + uint32 internal constant REWARDS_TO_TREASURY_DENOMINATOR = 10000; //MAX_LOCK: It is a constant. One WEEK Added as a tolerance. uint256 public maxLockPeriod; diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 17244d9..77f3a29 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -187,10 +187,11 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); stream.status = StreamStatus.ACTIVE; - uint256 updatedRewardTokenAmount = rewardTokenAmount - stream.percentToTreasury * rewardTokenAmount / 10000; + uint256 updatedRewardTokenAmount = rewardTokenAmount - stream.percentToTreasury * rewardTokenAmount / REWARDS_TO_TREASURY_DENOMINATOR; stream.rewardDepositAmount = updatedRewardTokenAmount; + _updateStreamsRewardsSchedules(streamId, updatedRewardTokenAmount); - + if (stream.schedule.reward[0] != stream.rewardDepositAmount) { revert BadStart(); } @@ -215,7 +216,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A /** * @dev A stream can be removed after all the rewards pending have been withdrawn. - * Stream can be removed by the Stream Manager which is Multisig as time of deployment. + * Stream can be removed by the Stream Manager which is Multisig at time of deployment. */ function removeStream(uint256 streamId, address streamFundReceiver) external override onlyRole(STREAM_MANAGER_ROLE) { if (streamId == 0) { From 8359aafacce3afd43483db1aaab1481155cb758c Mon Sep 17 00:00:00 2001 From: ssubik Date: Tue, 28 Mar 2023 16:07:19 +0545 Subject: [PATCH 40/56] added collateral tokens to treasury and added life time limit for multisig --- .solhint.json | 2 +- .../staking/interfaces/IStakingHandler.sol | 3 ++- .../dao/staking/packages/StakingHandler.sol | 27 ++++++++++++------- .../dao/staking/packages/StakingInternals.sol | 9 +++++-- contracts/dao/treasury/MultiSigWallet.sol | 7 +++++ .../prod/1_deploy_init_staking_proxy.js | 6 ++++- .../test/2_deploy_init_staking_proxy.js | 6 ++++- scripts/tests/dao/full-flow-demo.test.js | 7 ++--- .../dao/governance/multisig-treasury.test.js | 19 +++++++++++++ scripts/tests/dao/staking/staking.test.js | 14 ++++++---- 10 files changed, 74 insertions(+), 26 deletions(-) diff --git a/.solhint.json b/.solhint.json index 4ab14d9..bccb37d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,7 +3,7 @@ "plugins": [], "rules": { "compiler-version": ["error","0.8.16"], - "max-line-length": ["error", 155], + "max-line-length": ["error", 160], "reason-string": ["error", {"maxLength": 96}], "func-visibility": ["error", {"ignoreConstructors": true}], "not-rely-on-time": "error", diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index 9f8de03..18728d3 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -10,6 +10,7 @@ interface IStakingHandler { function initializeStaking( address _admin, address _vault, + address _treasury, address _mainToken, address _voteToken, Weight calldata _weight, @@ -54,7 +55,7 @@ interface IStakingHandler { function withdrawAllStreams() external; - function withdrawPenalty(address penaltyReceiver) external; + function withdrawPenalty() external; function updateVault(address _vault) external; diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 77f3a29..6e80abd 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -14,6 +14,7 @@ import "../../../common/SafeERC20Staking.sol"; contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, AdminPausable { using SafeERC20Staking for IERC20; bytes32 public constant STREAM_MANAGER_ROLE = keccak256("STREAM_MANAGER_ROLE"); + bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); error NotPaused(); error VaultNotSupported(address _vault); @@ -64,6 +65,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A function initializeStaking( address _admin, address _vault, + address _treasury, address _mainToken, address _voteToken, Weight calldata _weight, @@ -73,15 +75,15 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A uint256 _minLockPeriod ) external override initializer { rewardsCalculator = _rewardsContract; - _initializeStaking(_mainToken, _voteToken, _weight, _vault, _maxLocks, voteCoef.voteShareCoef, voteCoef.voteLockCoef); + _initializeStaking(_mainToken, _voteToken,_treasury, _weight, _vault, _maxLocks,voteCoef.voteShareCoef, voteCoef.voteLockCoef); if (!IVault(vault).isSupportedToken(_mainToken)) { revert UnsupportedToken(); } pausableInit(1, _admin); _grantRole(STREAM_MANAGER_ROLE, _admin); + _grantRole(TREASURY_ROLE, _treasury); maxLockPeriod = ONE_YEAR; minLockPeriod = _minLockPeriod; - treasury = _admin; } /** @@ -187,21 +189,24 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); stream.status = StreamStatus.ACTIVE; - uint256 updatedRewardTokenAmount = rewardTokenAmount - stream.percentToTreasury * rewardTokenAmount / REWARDS_TO_TREASURY_DENOMINATOR; - stream.rewardDepositAmount = updatedRewardTokenAmount; + uint256 rewardsTokenToTreasury = stream.percentToTreasury * rewardTokenAmount / REWARDS_TO_TREASURY_DENOMINATOR; - _updateStreamsRewardsSchedules(streamId, updatedRewardTokenAmount); + stream.rewardDepositAmount = rewardTokenAmount - rewardsTokenToTreasury; + if(stream.rewardDepositAmount < stream.maxDepositAmount){ + _updateStreamsRewardsSchedules(streamId, stream.rewardDepositAmount); + } + if (stream.schedule.reward[0] != stream.rewardDepositAmount) { revert BadStart(); } emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); - IERC20(stream.rewardToken).safeTransfer(treasury, rewardTokenAmount - updatedRewardTokenAmount); - _transfer(updatedRewardTokenAmount, stream.rewardToken); + IERC20(stream.rewardToken).safeTransfer(treasury, rewardsTokenToTreasury); + _transfer(stream.rewardDepositAmount, stream.rewardToken); } - /** + /* * @dev Proposed stream can be cancelled by Stream Manager, which at the time of deployment is Multisig */ function cancelStreamProposal(uint256 streamId) external override onlyRole(STREAM_MANAGER_ROLE) { @@ -386,11 +391,11 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Penalty accrued due to early unlocking can be withdrawn to some address, most likely the treasury. * Address with TREASURY_ROLE can access this function, which is Multisig at time of deployment */ - function withdrawPenalty(address penaltyReceiver) external override pausable(1) onlyRole(DEFAULT_ADMIN_ROLE) { + function withdrawPenalty() external override pausable(1) onlyRole(TREASURY_ROLE) { if (totalPenaltyBalance == 0) { revert ZeroPenalty(); } - _withdrawPenalty(penaltyReceiver); + _withdrawPenalty(treasury); } /** @@ -419,6 +424,8 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if (newTreasury == address(0)){ revert ZeroAddress(); } + _revokeRole(TREASURY_ROLE, treasury); + _grantRole(TREASURY_ROLE, newTreasury); treasury = newTreasury; } diff --git a/contracts/dao/staking/packages/StakingInternals.sol b/contracts/dao/staking/packages/StakingInternals.sol index 64d57d0..ff0948f 100644 --- a/contracts/dao/staking/packages/StakingInternals.sol +++ b/contracts/dao/staking/packages/StakingInternals.sol @@ -25,15 +25,17 @@ contract StakingInternals is RewardsInternals { function _initializeStaking( address _mainToken, address _voteToken, + address _treasury, Weight memory _weight, address _vault, uint256 _maxLockPositions, uint256 _voteShareCoef, uint256 _voteLockCoef ) internal { - _verifyStaking(_mainToken, _voteToken, _weight, _vault, _voteLockCoef); + _verifyStaking(_mainToken, _voteToken,_treasury, _weight, _vault, _voteLockCoef); mainToken = _mainToken; voteToken = _voteToken; + treasury = _treasury; weight = _weight; vault = _vault; maxLockPositions = _maxLockPositions; @@ -263,7 +265,7 @@ contract StakingInternals is RewardsInternals { } // solhint-disable code-complexity - function _verifyStaking(address _mainToken, address _voteToken, Weight memory _weight, address _vault, uint256 _voteLockCoef) internal pure { + function _verifyStaking(address _mainToken,address _voteToken,address _treasury,Weight memory _weight,address _vault,uint256 _voteLockCoef) internal pure { if (_mainToken == address(0x00)) { revert ZeroAddress(); } @@ -273,6 +275,9 @@ contract StakingInternals is RewardsInternals { if (_vault == address(0x00)) { revert ZeroAddress(); } + if (_treasury == address(0x00)) { + revert ZeroAddress(); + } if (_weight.maxWeightShares <= _weight.minWeightShares) { revert InvalidShareWeights(); } diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index c6dfaa3..5748aed 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -33,6 +33,8 @@ contract MultiSigWallet is IMultiSigWallet { mapping(address => EnumerableSet.UintSet) internal confirmedTransactionsByOwner; uint256 public constant MINIMUM_LIFETIME = 86400; //oneDay + //Most common used lifetime is 30 days, Maximum lifetime is 60 days which allows for more complex transactions to proceed with ample time + uint256 public constant MAXIMUM_LIFETIME = 60 * 86400; //60Days uint256 public constant MAX_OWNER_COUNT = 50; error TxDoesNotExist(); @@ -53,6 +55,7 @@ contract MultiSigWallet is IMultiSigWallet { error TargetCodeChanged(); error OwnerAlreadyExists(); error TxNotConfirmed(); + error LifetimeMaximumNotMet(); modifier onlyOwnerOrGov() { if (!isOwner[msg.sender] && governor != msg.sender) { @@ -120,6 +123,10 @@ contract MultiSigWallet is IMultiSigWallet { revert LifetimeMinimumNotMet(); } + if(_lifetime > MAXIMUM_LIFETIME){ + revert LifetimeMaximumNotMet(); + } + if (!_to.isContract()) { if (_data.length > 0 || _value == 0) { revert InsufficientValue(); diff --git a/scripts/migrations/prod/1_deploy_init_staking_proxy.js b/scripts/migrations/prod/1_deploy_init_staking_proxy.js index 2108b61..de2ce2b 100644 --- a/scripts/migrations/prod/1_deploy_init_staking_proxy.js +++ b/scripts/migrations/prod/1_deploy_init_staking_proxy.js @@ -79,6 +79,10 @@ module.exports = async function(deployer) { type: 'address', name: '_vault' }, + { + type: 'address', + name: '_treasury' + }, { type: 'address', name: '_mainToken' @@ -118,7 +122,7 @@ module.exports = async function(deployer) { type: 'uint256', name: '_minLockPeriod' }] - }, [MultiSigWallet.address, vaultService.address, MainToken.address, VMainToken.address, + }, [MultiSigWallet.address, vaultService.address, MultiSigWallet.address, MainToken.address, VMainToken.address, weightObject, voteObject, maxNumberOfLocks, RewardsCalculator.address, minimumLockingPeriod]); await deployer.deploy(StakingProxyAdmin, {gas:8000000}); diff --git a/scripts/migrations/test/2_deploy_init_staking_proxy.js b/scripts/migrations/test/2_deploy_init_staking_proxy.js index 39aa0dc..0e92ba8 100644 --- a/scripts/migrations/test/2_deploy_init_staking_proxy.js +++ b/scripts/migrations/test/2_deploy_init_staking_proxy.js @@ -86,6 +86,10 @@ module.exports = async function(deployer) { type: 'address', name: '_vault' }, + { + type: 'address', + name: '_treasury' + }, { type: 'address', name: '_mainToken' @@ -125,7 +129,7 @@ module.exports = async function(deployer) { type: 'uint256', name: '_minLockPeriod' }] - }, [MultiSigWallet.address, vaultService.address, MainToken.address, VMainToken.address, + }, [MultiSigWallet.address, vaultService.address,MultiSigWallet.address, MainToken.address, VMainToken.address, weightObject, voteObject, maxNumberOfLocks, RewardsCalculator.address,minimumLockingPeriod]); await deployer.deploy(StakingProxyAdmin, {gas:8000000}); diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index c66f045..1a021df 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -75,11 +75,8 @@ const _encodeWithdrawPenaltyFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'withdrawPenalty', type: 'function', - inputs: [{ - type: 'address', - name: 'penaltyReceiver' - }] - }, [_account]); + inputs: [] + }, []); return toRet; } diff --git a/scripts/tests/dao/governance/multisig-treasury.test.js b/scripts/tests/dao/governance/multisig-treasury.test.js index 5e960bf..29b1e98 100644 --- a/scripts/tests/dao/governance/multisig-treasury.test.js +++ b/scripts/tests/dao/governance/multisig-treasury.test.js @@ -285,6 +285,25 @@ describe('MultiSig Wallet', () => { {"from": BENEFICIARY})).toString()).to.equal(AMOUNT_OUT_TREASURY); }); }); + + describe("Maximum Lifetime", async() => { + it("Should revert for maximum lifetime", async() => { + let errorMessage = "revert"; + const SEVENTY_DAYS = 70 * 86400; + await shouldRevertAndHaveSubstring( + multiSigWallet.submitTransaction( + multiSigWallet.address, + EMPTY_BYTES, + encoded_add_owners_function, + SEVENTY_DAYS, + {"from": accounts[0]} + ), + errTypes.revert, + errorMessage + ); + }) + + }) }); diff --git a/scripts/tests/dao/staking/staking.test.js b/scripts/tests/dao/staking/staking.test.js index 7de1aca..d25c27f 100644 --- a/scripts/tests/dao/staking/staking.test.js +++ b/scripts/tests/dao/staking/staking.test.js @@ -170,11 +170,8 @@ const _encodeWithdrawPenaltyFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'withdrawPenalty', type: 'function', - inputs: [{ - type: 'address', - name: 'penaltyReceiver' - }] - }, [_account]); + inputs: [] + }, []); return toRet; } @@ -672,6 +669,10 @@ describe("Staking Test", () => { await streamReward1.approve(stakingService.address, RewardProposalAmountForAStream, {from:stream_rewarder_1}) await stakingService.createStream(1,RewardProposalAmountForAStream, {from: stream_rewarder_1}); await blockchain.mineBlock(await _getTimeStamp() + 20); + + const ShouldBeBalanceOfMultisigAfterCreatingStream = web3.utils.toWei('4', 'ether');// 800 * 50/10000 = 4 + const ActualBalanceOfMultisigAfterCreatingStream = await streamReward1.balanceOf(multiSigWallet.address); + assert.equal(ActualBalanceOfMultisigAfterCreatingStream.toString(),ShouldBeBalanceOfMultisigAfterCreatingStream.toString()) }) it("Should propose a second stream, stream - 2", async() => { @@ -742,6 +743,9 @@ describe("Staking Test", () => { const RewardProposalAmountForAStream = web3.utils.toWei('1000', 'ether'); await streamReward2.approve(stakingService.address, RewardProposalAmountForAStream, {from:stream_rewarder_2}) await stakingService.createStream(2,RewardProposalAmountForAStream, {from: stream_rewarder_2}); + const ShouldBeBalanceOfMultisigAfterCreatingStream = web3.utils.toWei('5', 'ether');// 1000 * 50/10000 = 5 + const ActualBalanceOfMultisigAfterCreatingStream = await streamReward2.balanceOf(multiSigWallet.address); + assert.equal(ActualBalanceOfMultisigAfterCreatingStream.toString(), ShouldBeBalanceOfMultisigAfterCreatingStream.toString()) }) it('Setup Locks for staker_3 and staker_4 reward tests', async() => { From 4ace5c07f1b850f52ab128e7dc1f6334cbda59ea Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 30 Mar 2023 17:55:15 +0545 Subject: [PATCH 41/56] staking - emergencyunlock and withdraw now withdraws all the stream --- contracts/dao/staking/packages/StakingHandler.sol | 11 +++++++++-- contracts/dao/treasury/MultiSigWallet.sol | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 6e80abd..8566c41 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -351,18 +351,25 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } /** - * @dev Disregard rewards for emergency unlock and withdraw + * @dev In case of emergency, unlock your tokens and withdraw your rewards. + * @notice This can be only executed only if the contract is at paused state. */ function emergencyUnlockAndWithdraw() external override { if (paused == 0) { revert NotPaused(); } + User storage userAccount = users[msg.sender]; uint256 numberOfLocks = locks[msg.sender].length; for (uint256 lockId = numberOfLocks; lockId >= 1; lockId--) { + _moveAllStreamRewardsToPending(msg.sender, lockId); uint256 stakeValue = locks[msg.sender][lockId - 1].amountOfToken; _unlock(stakeValue, stakeValue, lockId, msg.sender); } - _withdraw(MAIN_STREAM); + for (uint256 i; i < streams.length; i++) { + if (userAccount.pendings[i] != 0 && streams[i].status == StreamStatus.ACTIVE) { + _withdraw(i); + } + } } /** diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index 5748aed..d878361 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -55,7 +55,7 @@ contract MultiSigWallet is IMultiSigWallet { error TargetCodeChanged(); error OwnerAlreadyExists(); error TxNotConfirmed(); - error LifetimeMaximumNotMet(); + error LifetimeMaximumExceeded(); modifier onlyOwnerOrGov() { if (!isOwner[msg.sender] && governor != msg.sender) { @@ -124,7 +124,7 @@ contract MultiSigWallet is IMultiSigWallet { } if(_lifetime > MAXIMUM_LIFETIME){ - revert LifetimeMaximumNotMet(); + revert LifetimeMaximumExceeded(); } if (!_to.isContract()) { From 43d7b6a784cd1c609c1625b0cd4a786e1681f6af Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 31 Mar 2023 12:23:39 +0545 Subject: [PATCH 42/56] modifying few scripts --- config/newly-generated-transaction-index.json | 79 +------------------ scripts/units/helpers/transactionSaver.js | 10 ++- scripts/units/setup-multisig-owners.js | 7 +- 3 files changed, 15 insertions(+), 81 deletions(-) diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json index d39974a..9e26dfe 100644 --- a/config/newly-generated-transaction-index.json +++ b/config/newly-generated-transaction-index.json @@ -1,78 +1 @@ -{ - "txIndexCreateLock": [ - { - "id": 1, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000005" - } - ], - "transferFathomTokenFromMultisig": [ - { - "id": 1, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000006" - } - ], - "ApproveDexXDC": [ - { - "id": 1, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000007" - } - ], - "createPoolWithXDC": [ - { - "id": 1, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000009" - }, - { - "id": 2, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000000b" - }, - { - "id": 3, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004c" - }, - { - "id": 4, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004e" - }, - { - "id": 5, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000053" - }, - { - "id": 6, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000055" - }, - { - "id": 7, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000057" - }, - { - "id": 8, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005b" - }, - { - "id": 9, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005f" - }, - { - "id": 10, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000061" - } - ], - "SwapExactETHForTokens": [ - { - "id": 1, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000004f" - } - ], - "SwapTokensForExactETH": [ - { - "id": 1, - "txnIndex": "0x0000000000000000000000000000000000000000000000000000000000000051" - }, - { - "id": 2, - "txnIndex": "0x000000000000000000000000000000000000000000000000000000000000005d" - } - ] -} \ No newline at end of file +{} \ No newline at end of file diff --git a/scripts/units/helpers/transactionSaver.js b/scripts/units/helpers/transactionSaver.js index af563b0..62e5fe8 100644 --- a/scripts/units/helpers/transactionSaver.js +++ b/scripts/units/helpers/transactionSaver.js @@ -1,6 +1,14 @@ const fs = require('fs'); -const rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); +let rawdata; +if (fs.existsSync('../../../config/newly-generated-transaction-index.json')) { + rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); +} else { + // create new file + fs.writeFileSync('../../../config/newly-generated-transaction-index.json', '{}'); + rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); +} + const constants = require('./constants') async function saveTxnIndex( diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 09be468..9e6a94b 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -3,6 +3,9 @@ const fs = require('fs'); const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); + const COUNCIL_1_PLACEHOLDER = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; const COUNCIL_2_PLACEHOLDER = "0x01d2D3da7a42F64e7Dc6Ae405F169836556adC86"; @@ -22,10 +25,10 @@ const _encodeAddOwnersFunction = (_accounts) => { } module.exports = async function(deployer) { - + await txnHelper.submitAndExecute( _encodeAddOwnersFunction([COUNCIL_1_PLACEHOLDER, COUNCIL_2_PLACEHOLDER]), - MULTISIG_WALLET_ADDRESS, + addresses.multiSigWallet, "setupMultisigOwner" ) From 1db83c30ecd9900d4291dc0f667f92fb26ea9875 Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Sat, 1 Apr 2023 18:54:50 +0400 Subject: [PATCH 43/56] Minor review fixes --- .solhint.json | 2 +- contracts/common/SafeERC20Staking.sol | 1 + contracts/dao/staking/StakingStorage.sol | 1 - contracts/dao/staking/StakingStructs.sol | 2 +- .../staking/interfaces/IStakingHandler.sol | 1 + .../staking/packages/RewardsCalculator.sol | 4 +- .../dao/staking/packages/StakingHandler.sol | 54 ++++++++++++++----- .../dao/staking/packages/StakingInternals.sol | 11 +++- contracts/dao/treasury/MultiSigWallet.sol | 2 +- 9 files changed, 56 insertions(+), 22 deletions(-) diff --git a/.solhint.json b/.solhint.json index bccb37d..f33f8c0 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,7 +3,7 @@ "plugins": [], "rules": { "compiler-version": ["error","0.8.16"], - "max-line-length": ["error", 160], + "max-line-length": ["error", 150], "reason-string": ["error", {"maxLength": 96}], "func-visibility": ["error", {"ignoreConstructors": true}], "not-rely-on-time": "error", diff --git a/contracts/common/SafeERC20Staking.sol b/contracts/common/SafeERC20Staking.sol index 6e502be..781b4e7 100644 --- a/contracts/common/SafeERC20Staking.sol +++ b/contracts/common/SafeERC20Staking.sol @@ -22,6 +22,7 @@ library SafeERC20Staking { function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index 2dea31d..3acc935 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -57,5 +57,4 @@ contract StakingStorage { ///Mapping (user => LockedBalance) to keep locking information for each user mapping(address => LockedBalance[]) internal locks; mapping(uint256 => uint256) public streamTotalUserPendings; - } diff --git a/contracts/dao/staking/StakingStructs.sol b/contracts/dao/staking/StakingStructs.sol index 942149b..e50ffc5 100644 --- a/contracts/dao/staking/StakingStructs.sol +++ b/contracts/dao/staking/StakingStructs.sol @@ -54,7 +54,7 @@ struct LockedBalance { struct Stream { address owner; // stream owned by the ERC-20 reward token owner address manager; // stream manager handled by Main stream manager role - uint256 percentToTreasury; + uint256 percentToTreasury; address rewardToken; StreamStatus status; uint256 rewardDepositAmount; // the reward amount that has been deposited by third party diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index 18728d3..4ab824c 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -68,5 +68,6 @@ interface IStakingHandler { function setMinimumLockPeriod(uint256 _minLockPeriod) external; function setMaxLockPositions(uint256 newMaxLockPositions) external; + function setTreasuryAddress(address newTreasury) external; } diff --git a/contracts/dao/staking/packages/RewardsCalculator.sol b/contracts/dao/staking/packages/RewardsCalculator.sol index 16b9948..41b4705 100644 --- a/contracts/dao/staking/packages/RewardsCalculator.sol +++ b/contracts/dao/staking/packages/RewardsCalculator.sol @@ -8,7 +8,7 @@ import "../../../common/math/FullMath.sol"; // solhint-disable not-rely-on-time contract RewardsCalculator is IRewardsHandler { - uint256 public constant MAXIMUM_PERCENT_TO_TREASURY = 5000; //10000th + uint256 public constant MAXIMUM_PERCENT_TO_TREASURY = 10000; // equal to denominator error BadOwnerError(); error BadRewardTokenError(); error NoMinDepositError(); @@ -67,7 +67,7 @@ contract RewardsCalculator is IRewardsHandler { if (tau == 0) { revert BadTauError(); } - if (percentToTreasury > MAXIMUM_PERCENT_TO_TREASURY){ + if (percentToTreasury > MAXIMUM_PERCENT_TO_TREASURY) { revert BadPercentToTreasuryError(); } for (uint256 i = 1; i < scheduleTimes.length; i++) { diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 8566c41..e2283a6 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -46,6 +46,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error MaxLockPositionsReached(); error ZeroAmount(); error NotLockOwner(); + error BadRewardsAmount(); constructor() { _disableInitializers(); @@ -75,7 +76,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A uint256 _minLockPeriod ) external override initializer { rewardsCalculator = _rewardsContract; - _initializeStaking(_mainToken, _voteToken,_treasury, _weight, _vault, _maxLocks,voteCoef.voteShareCoef, voteCoef.voteLockCoef); + _initializeStaking(_mainToken, _voteToken, _treasury, _weight, _vault, _maxLocks, voteCoef.voteShareCoef, voteCoef.voteLockCoef); if (!IVault(vault).isSupportedToken(_mainToken)) { revert UnsupportedToken(); } @@ -100,14 +101,23 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A revert AlreadyInitialized(); } IERC20(mainToken).safeTransferFrom(msg.sender, address(this), scheduleRewards[0]); - _validateStreamParameters(_owner, mainToken, 0,scheduleRewards[MAIN_STREAM], scheduleRewards[MAIN_STREAM], scheduleTimes, scheduleRewards, tau); + _validateStreamParameters( + _owner, + mainToken, + 0, + scheduleRewards[MAIN_STREAM], + scheduleRewards[MAIN_STREAM], + scheduleTimes, + scheduleRewards, + tau + ); uint256 streamId = 0; Schedule memory schedule = Schedule(scheduleTimes, scheduleRewards); streams.push( Stream({ owner: _owner, manager: _owner, - percentToTreasury:0, + percentToTreasury: 0, rewardToken: mainToken, maxDepositAmount: 0, minDepositAmount: 0, @@ -146,14 +156,23 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A function proposeStream( address streamOwner, address rewardToken, - uint256 percentToTreasury, //10000th + uint256 percentToTreasury, uint256 maxDepositAmount, uint256 minDepositAmount, uint256[] calldata scheduleTimes, uint256[] calldata scheduleRewards, uint256 tau ) external override onlyRole(STREAM_MANAGER_ROLE) { - _validateStreamParameters(streamOwner, rewardToken, percentToTreasury, maxDepositAmount, minDepositAmount, scheduleTimes, scheduleRewards, tau); + _validateStreamParameters( + streamOwner, + rewardToken, + percentToTreasury, + maxDepositAmount, + minDepositAmount, + scheduleTimes, + scheduleRewards, + tau + ); if (!IVault(vault).isSupportedToken(rewardToken)) { revert UnsupportedToken(); } @@ -189,11 +208,15 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); stream.status = StreamStatus.ACTIVE; - uint256 rewardsTokenToTreasury = stream.percentToTreasury * rewardTokenAmount / REWARDS_TO_TREASURY_DENOMINATOR; - + + if (rewardTokenAmount < REWARDS_TO_TREASURY_DENOMINATOR) { + revert BadRewardsAmount(); + } + uint256 rewardsTokenToTreasury = (stream.percentToTreasury * rewardTokenAmount) / REWARDS_TO_TREASURY_DENOMINATOR; + stream.rewardDepositAmount = rewardTokenAmount - rewardsTokenToTreasury; - - if(stream.rewardDepositAmount < stream.maxDepositAmount){ + + if (stream.rewardDepositAmount < stream.maxDepositAmount) { _updateStreamsRewardsSchedules(streamId, stream.rewardDepositAmount); } @@ -202,7 +225,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); - IERC20(stream.rewardToken).safeTransfer(treasury, rewardsTokenToTreasury); + + if (rewardsTokenToTreasury > 0) { + IERC20(stream.rewardToken).safeTransfer(treasury, rewardsTokenToTreasury); + } _transfer(stream.rewardDepositAmount, stream.rewardToken); } @@ -366,9 +392,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A _unlock(stakeValue, stakeValue, lockId, msg.sender); } for (uint256 i; i < streams.length; i++) { - if (userAccount.pendings[i] != 0 && streams[i].status == StreamStatus.ACTIVE) { - _withdraw(i); - } + if (userAccount.pendings[i] != 0 && streams[i].status == StreamStatus.ACTIVE) { + _withdraw(i); + } } } @@ -428,7 +454,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } function setTreasuryAddress(address newTreasury) external override onlyRole(DEFAULT_ADMIN_ROLE) { - if (newTreasury == address(0)){ + if (newTreasury == address(0)) { revert ZeroAddress(); } _revokeRole(TREASURY_ROLE, treasury); diff --git a/contracts/dao/staking/packages/StakingInternals.sol b/contracts/dao/staking/packages/StakingInternals.sol index ff0948f..5c4a043 100644 --- a/contracts/dao/staking/packages/StakingInternals.sol +++ b/contracts/dao/staking/packages/StakingInternals.sol @@ -32,7 +32,7 @@ contract StakingInternals is RewardsInternals { uint256 _voteShareCoef, uint256 _voteLockCoef ) internal { - _verifyStaking(_mainToken, _voteToken,_treasury, _weight, _vault, _voteLockCoef); + _verifyStaking(_mainToken, _voteToken, _treasury, _weight, _vault, _voteLockCoef); mainToken = _mainToken; voteToken = _voteToken; treasury = _treasury; @@ -265,7 +265,14 @@ contract StakingInternals is RewardsInternals { } // solhint-disable code-complexity - function _verifyStaking(address _mainToken,address _voteToken,address _treasury,Weight memory _weight,address _vault,uint256 _voteLockCoef) internal pure { + function _verifyStaking( + address _mainToken, + address _voteToken, + address _treasury, + Weight memory _weight, + address _vault, + uint256 _voteLockCoef + ) internal pure { if (_mainToken == address(0x00)) { revert ZeroAddress(); } diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index d878361..906dd26 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -123,7 +123,7 @@ contract MultiSigWallet is IMultiSigWallet { revert LifetimeMinimumNotMet(); } - if(_lifetime > MAXIMUM_LIFETIME){ + if (_lifetime > MAXIMUM_LIFETIME) { revert LifetimeMaximumExceeded(); } From d17ca69b2452d5d566b17e67cc0da1923873768c Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Sat, 1 Apr 2023 21:45:29 +0400 Subject: [PATCH 44/56] Max lock positions bugfix --- contracts/dao/staking/packages/StakingHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index e2283a6..e24dc03 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -466,7 +466,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if (lockPeriod < minLockPeriod) { revert MinLockPeriodNotMet(); } - if (locks[account].length > maxLockPositions) { + if (locks[account].length >= maxLockPositions) { revert MaxLockPositionsReached(); } if (amount == 0) { From bff62e763628e5c81597a62fba25026abddc6758 Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 5 Apr 2023 11:21:13 +0545 Subject: [PATCH 45/56] tested emergency scenarios and made some changes to emergency unlock and with tests --- contracts/common/proxy/VaultProxy.sol | 2 +- contracts/common/proxy/VaultProxyAdmin.sol | 2 +- .../dao/staking/packages/StakingHandler.sol | 12 +- contracts/dao/test/VaultProxyAdminMigrate.sol | 7 + contracts/dao/test/VaultProxyMigrate.sol | 9 + .../test/1_deploy_test_contracts.js | 21 + .../staking-criticals.test.js} | 489 +++++++++++++++++- 7 files changed, 517 insertions(+), 25 deletions(-) create mode 100644 contracts/dao/test/VaultProxyAdminMigrate.sol create mode 100644 contracts/dao/test/VaultProxyMigrate.sol rename scripts/tests/dao/{demo/staking.demo.test.js => staking/staking-criticals.test.js} (55%) diff --git a/contracts/common/proxy/VaultProxy.sol b/contracts/common/proxy/VaultProxy.sol index 62c7bd8..d638616 100644 --- a/contracts/common/proxy/VaultProxy.sol +++ b/contracts/common/proxy/VaultProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL 3.0 // Original Copyright Aurora // Copyright Fathom 2022 -pragma solidity ^0.8.0; +pragma solidity 0.8.16; import "./transparent/TransparentUpgradeableProxy.sol"; contract VaultProxy is TransparentUpgradeableProxy { diff --git a/contracts/common/proxy/VaultProxyAdmin.sol b/contracts/common/proxy/VaultProxyAdmin.sol index 0fd1057..30f8b69 100644 --- a/contracts/common/proxy/VaultProxyAdmin.sol +++ b/contracts/common/proxy/VaultProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL 3.0 // Original Copyright Aurora // Copyright Fathom 2022 -pragma solidity ^0.8.0; +pragma solidity 0.8.16; import "./transparent/ProxyAdmin.sol"; contract VaultProxyAdmin is ProxyAdmin {} diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index e24dc03..782e631 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -19,6 +19,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error NotPaused(); error VaultNotSupported(address _vault); error VaultNotMigrated(address _vault); + error VaultMigrated(address _vault); error ZeroLockId(); error MaxLockPeriodExceeded(); error MaxLockIdExceeded(uint256 _lockId, address _account); @@ -47,6 +48,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error ZeroAmount(); error NotLockOwner(); error BadRewardsAmount(); + constructor() { _disableInitializers(); @@ -377,20 +379,26 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } /** - * @dev In case of emergency, unlock your tokens and withdraw your rewards. + * @dev In case of emergency, unlock your tokens and withdraw all your rewards you have already claimed. + * @notice This function does neglects all the rewards yet to be claimed in emergency * @notice This can be only executed only if the contract is at paused state. + * @notice This function can only be called if VaultContract is not compromised and vault is not at paused state. */ function emergencyUnlockAndWithdraw() external override { if (paused == 0) { revert NotPaused(); } + if (IVault(vault).migrated()) { + revert VaultMigrated(vault); + } + User storage userAccount = users[msg.sender]; uint256 numberOfLocks = locks[msg.sender].length; for (uint256 lockId = numberOfLocks; lockId >= 1; lockId--) { - _moveAllStreamRewardsToPending(msg.sender, lockId); uint256 stakeValue = locks[msg.sender][lockId - 1].amountOfToken; _unlock(stakeValue, stakeValue, lockId, msg.sender); } + //withdraw if any rewards are already claimed and pending to be withdrawn. for (uint256 i; i < streams.length; i++) { if (userAccount.pendings[i] != 0 && streams[i].status == StreamStatus.ACTIVE) { _withdraw(i); diff --git a/contracts/dao/test/VaultProxyAdminMigrate.sol b/contracts/dao/test/VaultProxyAdminMigrate.sol new file mode 100644 index 0000000..ee251b3 --- /dev/null +++ b/contracts/dao/test/VaultProxyAdminMigrate.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL 3.0 +// Original Copyright Aurora +// Copyright Fathom 2022 +pragma solidity 0.8.16; +import "../../common/proxy/transparent/ProxyAdmin.sol"; + +contract VaultProxyAdminMigrate is ProxyAdmin {} diff --git a/contracts/dao/test/VaultProxyMigrate.sol b/contracts/dao/test/VaultProxyMigrate.sol new file mode 100644 index 0000000..54bc6c8 --- /dev/null +++ b/contracts/dao/test/VaultProxyMigrate.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: AGPL 3.0 +// Original Copyright Aurora +// Copyright Fathom 2022 +pragma solidity 0.8.16; +import "../../common/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract VaultProxyMigrate is TransparentUpgradeableProxy { + constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} +} diff --git a/scripts/migrations/test/1_deploy_test_contracts.js b/scripts/migrations/test/1_deploy_test_contracts.js index 70c0d3e..9739237 100644 --- a/scripts/migrations/test/1_deploy_test_contracts.js +++ b/scripts/migrations/test/1_deploy_test_contracts.js @@ -9,6 +9,10 @@ const TokenTimelock = artifacts.require("./dao/test/token-timelock/TokenTimelock const MainToken = artifacts.require("./dao/tokens/MainToken.sol"); const oneYr = 365 * 24 * 60 * 60; +const VaultProxyAdmin = artifacts.require('./dao/test/VaultProxyAdminMigrate.sol'); +const VaultProxy = artifacts.require('./dao/test/VaultProxyMigrate.sol') +const Vault = artifacts.require('./dao/staking/vault/packages/VaultPackage.sol'); +const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const _getTimeStamp = async () => { const timestamp = await blockchain.getLatestBlockTimestamp() @@ -30,4 +34,21 @@ module.exports = async function(deployer) { ]; await Promise.all(promises); + + let toInitialize = web3.eth.abi.encodeFunctionCall({ + name: 'initVault', + type: 'function', + inputs: [{ + type: 'address', + name: '_admin' + }, + { + type: 'address[]', + name: 'supportedTokens' + } + ] + }, [MultiSigWallet.address,[MainToken.address]]); + + await deployer.deploy(VaultProxyAdmin, {gas:8000000}); + await deployer.deploy(VaultProxy, Vault.address, VaultProxyAdmin.address, toInitialize, {gas:8000000}); }; diff --git a/scripts/tests/dao/demo/staking.demo.test.js b/scripts/tests/dao/staking/staking-criticals.test.js similarity index 55% rename from scripts/tests/dao/demo/staking.demo.test.js rename to scripts/tests/dao/staking/staking-criticals.test.js index 40baf0a..25f0019 100644 --- a/scripts/tests/dao/demo/staking.demo.test.js +++ b/scripts/tests/dao/staking/staking-criticals.test.js @@ -107,6 +107,32 @@ const _encodeUpgradeFunction = (_proxy, _impl) => { return toRet; } +const _encodeUpdateVault = (_vault) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'updateVault', + type: 'function', + inputs: [{ + type: 'address', + name: '_vault' + }] + }, [_vault]); + + return toRet; +} + +const _encodeMigrate = (_newVaultPackage) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'migrate', + type: 'function', + inputs: [{ + type: 'address', + name: 'newVaultPackage' + }] + }, [_newVaultPackage]); + + return toRet; +} + const _encodeAddSupportedTokenFunction = (_token) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'addSupportedToken', @@ -120,6 +146,18 @@ const _encodeAddSupportedTokenFunction = (_token) => { return toRet; } +const _encodeAdminPause = (flag) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'adminPause', + type: 'function', + inputs: [{ + type: 'uint256', + name: 'flags' + }]}, [flag]); + + return toRet; +} + const _encodeProposeStreamFunction = ( _owner, _rewardToken, @@ -173,7 +211,9 @@ const _encodeProposeStreamFunction = ( return toRet; } -describe("Staking Test and Upgrade Test", () => { + + +describe("Staking Test, Upgrade Test and Emergency Scenarios", () => { const oneYear = 31556926; let stakingService; @@ -200,12 +240,13 @@ describe("Staking Test and Upgrade Test", () => { let proxyAddress; let vaultProxyAdmin; let stakingProxyAdmin; - + let vaultMigrateService; + let snapshotToRevertTo; const sumToDeposit = web3.utils.toWei('100', 'ether'); - const sumToTransfer = web3.utils.toWei('4000', 'ether'); - const sumToApprove = web3.utils.toWei('3000','ether'); - const sumForProposer = web3.utils.toWei('3000','ether') + const sumToTransfer = web3.utils.toWei('2000', 'ether'); + const sumToApprove = web3.utils.toWei('2000','ether'); + const sumForProposer = web3.utils.toWei('20000','ether') const vMainTokensToApprove = web3.utils.toWei('500000', 'ether') before(async() => { @@ -262,6 +303,11 @@ describe("Staking Test and Upgrade Test", () => { "StakingProxyAdmin" ) + vaultMigrateService = await artifacts.initializeInterfaceAt( + "IVault", + "VaultProxyMigrate" + ) + FTHMToken = await artifacts.initializeInterfaceAt("MainToken","MainToken"); streamReward1 = await artifacts.initializeInterfaceAt("ERC20Rewards1","ERC20Rewards1"); streamReward2 = await artifacts.initializeInterfaceAt("ERC20Rewards2","ERC20Rewards2"); @@ -545,7 +591,7 @@ describe("Staking Test and Upgrade Test", () => { }); describe('Creating Streams and Rewards Calculations', async() => { - it("Should propose a second stream, stream - 1", async() => { + it("Should propose first stream, stream - 1", async() => { console.log("A protocol wanting to collaborate with us, proposes a stream") console.log("They provide us their native tokens that they want to distribute to the community") console.log(".........Creating a Proposal for a stream..........") @@ -564,8 +610,8 @@ describe("Staking Test and Upgrade Test", () => { ]; const _proposeStreamFromMultiSigTreasury = async ( - _stream_rewarder_2, - _streamReward2Address, + _stream_rewarder_1, + _streamReward1Address, _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, @@ -577,8 +623,8 @@ describe("Staking Test and Upgrade Test", () => { stakingService.address, EMPTY_BYTES, _encodeProposeStreamFunction( - _stream_rewarder_2, - _streamReward2Address, + _stream_rewarder_1, + _streamReward1Address, _percentToTreasury, _maxRewardProposalAmountForAStream, _minRewardProposalAmountForAStream, @@ -598,8 +644,8 @@ describe("Staking Test and Upgrade Test", () => { } await _proposeStreamFromMultiSigTreasury( - stream_rewarder_2, - streamReward2Address, + stream_rewarder_1, + streamReward1Address, percentToTreasury, maxRewardProposalAmountForAStream, minRewardProposalAmountForAStream, @@ -616,8 +662,8 @@ describe("Staking Test and Upgrade Test", () => { console.log(".........Creating the stream proposed.........") console.log("Once create stream is called, the proposal will become live once start time is reached") const RewardProposalAmountForAStream = web3.utils.toWei('1000', 'ether'); - await streamReward2.approve(stakingService.address, RewardProposalAmountForAStream, {from:stream_rewarder_2}) - await stakingService.createStream(streamId,RewardProposalAmountForAStream, {from: stream_rewarder_2}); + await streamReward1.approve(stakingService.address, RewardProposalAmountForAStream, {from:stream_rewarder_1}) + await stakingService.createStream(streamId,RewardProposalAmountForAStream, {from: stream_rewarder_1}); }) it("Should grant admin role to accounts[0] and withdraw extra supported tokens", async() =>{ @@ -644,18 +690,419 @@ describe("Staking Test and Upgrade Test", () => { await vaultService.withdrawExtraSupportedTokens(accounts[0], {"from": accounts[0]}); }) + }) + describe('Scenario - to pause staking service in case of emergency shutdown, let users to withdraw using emergencyUnlockAndWithdraw and then pause the Vault Contract as well', async() => { + it('Should make lock position staker_4 - here we create snapshot', async() => { + snapshotToRevertTo = await blockchain.createSnapshot() + await FTHMToken.approve(stakingService.address, sumToApprove, {from: staker_4}) + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_4, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) - it('Should not be initalizable twice', async() => { - const errorMessage = "Initializable: contract is already initialized"; - shouldRevert( - vMainToken.initToken(multiSigWallet.address, stakingService.address, {gas: 8000000}), - errTypes.revert, - errorMessage - ); + it('Should make lock position staker_4', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_4, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + it("Should pause the staking in case of emergency", async() => { + const toPauseFlag = 1 + const _pauseContract = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + _contract.address, + EMPTY_BYTES, + _encodeAdminPause(toPauseFlag), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _pauseContract(stakingService) + + }) + + + it("Should emergency unlock locked position for staker_3 - emergency unlock will be available for certain time", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + await stakingService.emergencyUnlockAndWithdraw({"from": staker_3, gas: 30000000}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should add support for tokens for new VaultPackageMigrate", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _addSupportedTokenFromMultiSigTreasury = async (_token) => { + const result = await multiSigWallet.submitTransaction( + vaultMigrateService.address, + EMPTY_BYTES, + _encodeAddSupportedTokenFunction(_token), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _addSupportedTokenFromMultiSigTreasury(streamReward1Address) + await _addSupportedTokenFromMultiSigTreasury(streamReward2Address) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + + it("Should grant role of rewards operator to new vaultMigrateService", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const roleHash = web3.utils.soliditySha3('REWARDS_OPERATOR_ROLE'); + const _grantRoleMultisig = async (_role, _account) => { + const result = await multiSigWallet.submitTransaction( + vaultMigrateService.address, + EMPTY_BYTES, + _encodeGrantRole(_role, _account), + 0, + {"from": accounts[0]} + ); + txIndex = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(txIndex, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(txIndex, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(txIndex, {"from": accounts[1]}); + } + + await _grantRoleMultisig( + roleHash, + vaultService.address + ) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should pause the vault in case of emergency", async() => { + const toPauseFlag = 1 + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _pauseContract = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + _contract.address, + EMPTY_BYTES, + _encodeAdminPause(toPauseFlag), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _pauseContract(vaultService) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + it("Should migrate original vault tokens to vaultMigrateService", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _migrateVaultTokens = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + vaultService.address, + EMPTY_BYTES, + _encodeMigrate(_contract), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + await _migrateVaultTokens(vaultMigrateService.address) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should revert emergency unlock as vault does not have enough tokens after migration", async() => { + let errorMessage = "revert"; + await blockchain.mineBlock(await _getTimeStamp() + 100); + await shouldRevertAndHaveSubstring( + stakingService.emergencyUnlockAndWithdraw( + {"from": staker_4, gas: 30000000}), + errTypes.revert, + errorMessage); + await blockchain.mineBlock(await _getTimeStamp() + 100); }) }) + + describe('Scenario - when the vault is compromised and we need to pause the vault and update vault to new address', async() => { + it('Should make lock position staker_4 - here we revert to previous snapshot', async() => { + await blockchain.revertToSnapshot(snapshotToRevertTo); + await FTHMToken.approve(stakingService.address, sumToApprove, {from: staker_4}) + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_4, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_4', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_4, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should pause the vault in case of emergency", async() => { + const toPauseFlag = 1 + const _pauseContract = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + _contract.address, + EMPTY_BYTES, + _encodeAdminPause(toPauseFlag), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _pauseContract(vaultService) + + }) + + it("Should revert on creating locks and vault is paused", async() => { + let errorMessage = "paused contract"; + await blockchain.mineBlock(await _getTimeStamp() + 100); + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + await shouldRevert( + stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}), + errTypes.revert, + errorMessage); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should pause the staking contract to update vault", async() => { + const toPauseFlag = 1 + const _pauseContract = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + _contract.address, + EMPTY_BYTES, + _encodeAdminPause(toPauseFlag), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _pauseContract(stakingService) + + }) + + it("Should revert emergency unlock as vault is paused", async() => { + let errorMessage = "revert"; + await blockchain.mineBlock(await _getTimeStamp() + 100); + await shouldRevertAndHaveSubstring( + stakingService.emergencyUnlockAndWithdraw( + {"from": staker_4, gas: 30000000}), + errTypes.revert, + errorMessage); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + it("Should add support for tokens for new VaultPackageMigrate", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _addSupportedTokenFromMultiSigTreasury = async (_token) => { + const result = await multiSigWallet.submitTransaction( + vaultMigrateService.address, + EMPTY_BYTES, + _encodeAddSupportedTokenFunction(_token), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + + await _addSupportedTokenFromMultiSigTreasury(streamReward1Address) + await _addSupportedTokenFromMultiSigTreasury(streamReward2Address) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + it("Should grant role of rewards operator to old vault service for new vault package", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const roleHash = web3.utils.soliditySha3('REWARDS_OPERATOR_ROLE'); + const _grantRoleMultisig = async (_role, _account) => { + const result = await multiSigWallet.submitTransaction( + vaultMigrateService.address, + EMPTY_BYTES, + _encodeGrantRole(_role, _account), + 0, + {"from": accounts[0]} + ); + txIndex = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(txIndex, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(txIndex, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(txIndex, {"from": accounts[1]}); + } + + await _grantRoleMultisig( + roleHash, + vaultService.address + ) + + await _grantRoleMultisig( + roleHash, + stakingService.address + ) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + + it("Should migrate original vault tokens to vaultMigrateService", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _migrateVaultTokens = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + vaultService.address, + EMPTY_BYTES, + _encodeMigrate(_contract), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + await _migrateVaultTokens(vaultMigrateService.address) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should update vault", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 100); + const _updateVault = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + stakingService.address, + EMPTY_BYTES, + _encodeUpdateVault(_contract), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + await _updateVault(vaultMigrateService.address) + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + + it("Should unpause staking and the normal create locking should work", async() => { + const toUnpauseFlag = 0 + const _unpauseContract = async (_contract) => { + const result = await multiSigWallet.submitTransaction( + _contract.address, + EMPTY_BYTES, + _encodeAdminPause(toUnpauseFlag), + 0, + {"from": accounts[0]} + ); + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + + await _unpauseContract(stakingService) + }) + + it('Should make lock position staker_3', async() => { + const unlockTime = 20 * 24 * 60 * 60 + await blockchain.mineBlock(await _getTimeStamp() + 100); + let result3 = await stakingService.createLock(sumToDeposit,unlockTime,{from: staker_3, gas: maxGasForTxn}); + await blockchain.mineBlock(await _getTimeStamp() + 100); + }) + }) + }); From 48dd49c02b67130cc74e69466dd70d560b12bb1f Mon Sep 17 00:00:00 2001 From: ssubik Date: Wed, 5 Apr 2023 17:37:11 +0545 Subject: [PATCH 46/56] withdraw only Main Stream tokens on emergency unlock and withdraw plus some minor changes --- .../dao/staking/packages/StakingHandler.sol | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 782e631..c972fb3 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -48,7 +48,6 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A error ZeroAmount(); error NotLockOwner(); error BadRewardsAmount(); - constructor() { _disableInitializers(); @@ -205,7 +204,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A */ function createStream(uint256 streamId, uint256 rewardTokenAmount) external override pausable(1) { Stream storage stream = streams[streamId]; - _verifyStream(stream, rewardTokenAmount); + _verifyStream(stream); IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); @@ -226,6 +225,13 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A revert BadStart(); } + if (stream.rewardDepositAmount > stream.maxDepositAmount) { + revert RewardsTooHigh(); + } + if (stream.rewardDepositAmount < stream.minDepositAmount) { + revert RewardsTooLow(); + } + emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); if (rewardsTokenToTreasury > 0) { @@ -379,9 +385,9 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } /** - * @dev In case of emergency, unlock your tokens and withdraw all your rewards you have already claimed. - * @notice This function does neglects all the rewards yet to be claimed in emergency - * @notice This can be only executed only if the contract is at paused state. + * @dev In case of emergency, unlock your tokens and withdraw all your position + * @notice This function neglects all the rewards + * @notice This can be executed only if the contract is at paused state. * @notice This function can only be called if VaultContract is not compromised and vault is not at paused state. */ function emergencyUnlockAndWithdraw() external override { @@ -391,19 +397,13 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if (IVault(vault).migrated()) { revert VaultMigrated(vault); } - - User storage userAccount = users[msg.sender]; + //unlock all locks uint256 numberOfLocks = locks[msg.sender].length; for (uint256 lockId = numberOfLocks; lockId >= 1; lockId--) { uint256 stakeValue = locks[msg.sender][lockId - 1].amountOfToken; _unlock(stakeValue, stakeValue, lockId, msg.sender); } - //withdraw if any rewards are already claimed and pending to be withdrawn. - for (uint256 i; i < streams.length; i++) { - if (userAccount.pendings[i] != 0 && streams[i].status == StreamStatus.ACTIVE) { - _withdraw(i); - } - } + _withdraw(MAIN_STREAM); } /** @@ -495,7 +495,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A IVault(vault).deposit(_token, _amount); } - function _verifyStream(Stream memory stream, uint256 rewardTokenAmount) internal view { + function _verifyStream(Stream memory stream) internal view { if (stream.status != StreamStatus.PROPOSED) { revert NotProposed(); } @@ -505,13 +505,6 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A if (stream.owner != msg.sender) { revert NotOwner(); } - - if (rewardTokenAmount > stream.maxDepositAmount) { - revert RewardsTooHigh(); - } - if (rewardTokenAmount < stream.minDepositAmount) { - revert RewardsTooLow(); - } } function _verifyUnlock(uint256 lockId) internal view { From 20638f8c55b97698ec2bf34c043bb6ffc823a92b Mon Sep 17 00:00:00 2001 From: Anton Grigorev Date: Thu, 6 Apr 2023 00:08:55 +0400 Subject: [PATCH 47/56] Minor fixes --- config/external-addresses.json | 21 ------------------- config/newly-generated-transaction-index.json | 1 - config/stablecoin-addresses-proxy-wallet.json | 1 - .../dao/staking/packages/StakingHandler.sol | 3 +-- 4 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 config/external-addresses.json delete mode 100644 config/newly-generated-transaction-index.json delete mode 100644 config/stablecoin-addresses-proxy-wallet.json diff --git a/config/external-addresses.json b/config/external-addresses.json deleted file mode 100644 index a173c32..0000000 --- a/config/external-addresses.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "positionManager":"0xa14385249806Fa27Bed76CCE444845aF97C1B5f9", - "stabilityFeeCollector":"0xbe169d7280D789159e4F4a38626818fbA76c0B19", - "xdcAdapter":"0x61D0f739Ab0f199607024Fe589cBBdC1bB90293F", - "stablecoinAdapter":"0x176486E65FEAEc0B831C9254154757B0e2676668", - "collateralPoolId":"0x5844430000000000000000000000000000000000000000000000000000000000", - "DEX_ROUTER_ADDRESS":"0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95", - "PROXY_WALLET_REGISTRY_ADDRESS":"0x06063CeB65f66A678812e753785D00237F60564A", - "STABLE_SWAP_ADDRESS":"0xa47232D1c5608996D4168222c45ed17E3947a50a", - "USD_ADDRESS":"0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f", - "FXD_ADDRESS":"0x429758F17E06eD5cF60f4382442592021158B578", - "SHOW_STOPPER_ADDRESS":"", - "SYSTEM_DEBT_ENGINE_ADDRESS":"", - "STABILIITY_FEE_COLLECTOR_ADDRESS":"", - "PRICE_ORACLE_ADDRESS":"", - "BOOK_KEEPER_ADDRESS":"", - "POSITION_MANAGER_ADDRESS":"", - "COLLATERAL_POOL_CONFIG_ADDRESS":"", - "DEX_FACTORY_ADDRESS":"", - "WETH_ADDRESS":"0xE99500AB4A413164DA49Af83B9824749059b46ce" -} \ No newline at end of file diff --git a/config/newly-generated-transaction-index.json b/config/newly-generated-transaction-index.json deleted file mode 100644 index 9e26dfe..0000000 --- a/config/newly-generated-transaction-index.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/config/stablecoin-addresses-proxy-wallet.json b/config/stablecoin-addresses-proxy-wallet.json deleted file mode 100644 index 986d828..0000000 --- a/config/stablecoin-addresses-proxy-wallet.json +++ /dev/null @@ -1 +0,0 @@ -{"proxyWallet":"0xf5f4Db6F879F67d729dC1a6E246Cd80B2b037eBa"} \ No newline at end of file diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index c972fb3..42df4aa 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -206,8 +206,6 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A Stream storage stream = streams[streamId]; _verifyStream(stream); - IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); - stream.status = StreamStatus.ACTIVE; if (rewardTokenAmount < REWARDS_TO_TREASURY_DENOMINATOR) { @@ -234,6 +232,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A emit StreamCreated(streamId, stream.owner, stream.rewardToken, stream.tau); + IERC20(stream.rewardToken).safeTransferFrom(msg.sender, address(this), rewardTokenAmount); if (rewardsTokenToTreasury > 0) { IERC20(stream.rewardToken).safeTransfer(treasury, rewardsTokenToTreasury); } From 346c53af35ae8da48f94e11025daeae253fecfc7 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 6 Apr 2023 15:56:43 +0545 Subject: [PATCH 48/56] working on removing config --- .gitignore | 1 + docs/SCENARIOS-INSTRUCTIONS.md | 11 +++---- scripts/units/DEX/set-fee-to-setter.js | 5 ++-- scripts/units/DEX/set-fee-to.js | 5 ++-- scripts/units/add-liquidity-to-pool.js | 7 ++--- scripts/units/add-liquidity-to-xdc-pool.js | 9 +++--- scripts/units/create_pool_dex.js | 6 ++-- scripts/units/create_pool_dex_xdc.js | 6 ++-- .../units/create_stablecoin_open_position.js | 14 ++++----- .../units/create_stablecoin_proxy_wallet.js | 30 ++++++++++++++----- scripts/units/execute-proposals.js | 6 ++-- scripts/units/helpers/constants.js | 2 -- scripts/units/helpers/transactionSaver.js | 20 +++++++++---- scripts/units/setup_council_stakes.js | 8 ++--- .../book-keeper/allowlist-bookkeeper.js | 6 ++-- .../book-keeper/blocklist-bookkeeper.js | 6 ++-- .../init-collateral-pool.js | 6 ++-- .../collateral-pool-config/set-adapter.js | 6 ++-- .../set-close-factor-bps.js | 6 ++-- .../set-debt-accumulated-rate.js | 6 ++-- .../set-debt-ceiling.js | 6 ++-- .../collateral-pool-config/set-debt-floor.js | 6 ++-- .../set-liquidation-ratio.js | 6 ++-- .../set-liquidator-incentive-bps.js | 6 ++-- .../collateral-pool-config/set-price-feed.js | 6 ++-- .../set-price-with-safety-margin.js | 6 ++-- .../set-stability-fee-rate.js | 6 ++-- .../collateral-pool-config/set-strategy.js | 6 ++-- .../set-total-debt-share.js | 6 ++-- .../set-treausry-fees-bps.js | 6 ++-- .../delay-price-feed-pause.js | 6 ++-- .../delay-price-feed-unpause.js | 6 ++-- .../set-access-control-config.js | 6 ++-- .../set-oracle.js | 6 ++-- .../set-price-life.js | 6 ++-- .../set-price.js | 6 ++-- .../set-time-delay.js | 6 ++-- .../set-token-one.js | 6 ++-- .../set-token-zero.js | 6 ++-- .../flash-mint-module/set-fee-rate.js | 6 ++-- .../position-manager/set-price-oracle.js | 6 ++-- .../position-manager/stableswap-pause.js | 6 ++-- .../position-manager/stableswap-unpause.js | 6 ++-- .../price-oracle/price-oracle-cage.js | 6 ++-- .../price-oracle/price-oracle-pause.js | 6 ++-- .../price-oracle/price-oracle-uncage.js | 6 ++-- .../price-oracle/price-oracle-unpause.js | 6 ++-- .../stablecoin/price-oracle/set-price.js | 6 ++-- .../set-stable-coin-reference-price.js | 6 ++-- .../proxy-actions-storage/set-proxy-action.js | 6 ++-- .../units/stablecoin/showstopper/cage-pool.js | 6 ++-- .../showstopper/cage-showstopper.js | 6 ++-- .../stablecoin/showstopper/set-book-keeper.js | 6 ++-- .../showstopper/set-cage-cool-down.js | 6 ++-- .../showstopper/set-liquidation-engine.js | 6 ++-- .../showstopper/set-price-oracle.js | 6 ++-- .../showstopper/set-system-debt-engine.js | 6 ++-- .../collect_stability_fee.js | 6 ++-- .../set-system-debt-engine.js | 6 ++-- .../stability-fee-pause.js | 6 ++-- .../stability-fee-unpause.js | 6 ++-- .../stableswap/emergency-withdraw.js | 6 ++-- .../units/stablecoin/stableswap/set-fee-in.js | 6 ++-- .../stablecoin/stableswap/set-fee-out.js | 6 ++-- .../stablecoin/stableswap/stableswap-pause.js | 6 ++-- .../stableswap/stableswap-unpause.js | 6 ++-- .../stablecoin/stableswap/withdraw-fees.js | 6 ++-- .../system-debt-engine/set-surplus-buffer.js | 6 ++-- .../system-debt-engine-pause.js | 6 ++-- .../system-debt-engine-unpause.js | 6 ++-- .../withdraw-collateral-surplus.js | 6 ++-- .../withdraw-stablecoin-surplus.js | 6 ++-- .../units/stableswap-daily-limit-update.js | 6 ++-- scripts/units/stableswap-setup.js | 10 +++---- scripts/units/swap-Exact-ETH-For-Tokens.js | 10 +++---- scripts/units/swap-Exact-Tokens-For-Tokens.js | 8 ++--- scripts/units/swap-Tokens-For-Exact-ETH.js | 10 +++---- template/config-template.js | 24 +++++++++++++++ 78 files changed, 294 insertions(+), 252 deletions(-) create mode 100644 template/config-template.js diff --git a/.gitignore b/.gitignore index 4576df2..140a0a7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ yarn.lock package-lock.json bin addresses.json +/config diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index 185073a..d411b9e 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -19,16 +19,17 @@ ## How is file Structured? -In config/external-addresses.json file all the addresses are stored that needs to be used externally +In config/config.js file all the addresses are stored that needs to be used externally In config/newly-generated-transaction-index.json file all the newly generated transaction indexes are stored. Note: It would be a good practise to make this file empty for each new deployment you do and each new deployment in another branch. -config/external-addresses.json, config/newly-generated-transaction-index.json, stablecoin-addresses-proxy-wallet.json should not be deleted or the scenarios wont work - ## How to setup at first: 1. First you need to have addresses.json file in your root folder. This is automatically setup while doing deployment. -2. newly-generated-transaction-index.json must be made empty first for all the new deployment and must be taken into account that this does not in its own have context of the deployment. So for each new deployment-possibly on different branches, newly-generated-transaction-index.json must be cleared and then again it will be saved. -3. external-addresses.json must have the correct addresses as per newer deployments. +2. A config folder needs to be created at root folder. There is a template folder in root folder named config-template.js. Copy the config-template.js and paste it in config directory and rename it with config.js + +So basically you need to end up with config/config.js file path in your root folder for the scripts to work + + ## How to Setup Council Stakes **//Note: This is only possible once as theres initializer for council stakes.** diff --git a/scripts/units/DEX/set-fee-to-setter.js b/scripts/units/DEX/set-fee-to-setter.js index 8b7dfc1..edc7e1d 100644 --- a/scripts/units/DEX/set-fee-to-setter.js +++ b/scripts/units/DEX/set-fee-to-setter.js @@ -1,9 +1,8 @@ const fs = require('fs'); const txnHelper = require('../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); +const addressesConfig = require('../../../config/config.js') -const DEX_FACTORY_ADDRESS =addressesExternal.DEX_FACTORY_ADDRESS +const DEX_FACTORY_ADDRESS =addressesConfig.DEX_FACTORY_ADDRESS const FEE_TO_SETTER = "" const _encodeSetFeeToSetter = (_feeToSetter) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/DEX/set-fee-to.js b/scripts/units/DEX/set-fee-to.js index 5dc8ea4..3d9469a 100644 --- a/scripts/units/DEX/set-fee-to.js +++ b/scripts/units/DEX/set-fee-to.js @@ -1,9 +1,8 @@ const fs = require('fs'); const txnHelper = require('../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); +const addressesConfig = require('../../../config/config.js') -const DEX_FACTORY_ADDRESS =addressesExternal.DEX_FACTORY_ADDRESS +const DEX_FACTORY_ADDRESS =addressesConfig.DEX_FACTORY_ADDRESS const TO_BE_WHITELISTED = "0x" const FEE_TO = "" const _encodeSetFeeTo = (_feeTo) => { diff --git a/scripts/units/add-liquidity-to-pool.js b/scripts/units/add-liquidity-to-pool.js index 0b4946e..a6f7685 100644 --- a/scripts/units/add-liquidity-to-pool.js +++ b/scripts/units/add-liquidity-to-pool.js @@ -7,17 +7,14 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); +const addressesConfig = require('../../config/config.js') const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC // SET AS Necessary const Amount_A_Desired = web3.utils.toWei('2', 'ether') -const Amount_B_Desired = web3.utils.toWei('38', 'ether') const Amount_A_Minimum = web3.utils.toWei('0', 'ether') -const Amount_B_Minimum = web3.utils.toWei('0', 'ether') // const Amount_A_Desired = web3.utils.toWei('250000', 'ether') // const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') @@ -25,7 +22,7 @@ const Amount_B_Minimum = web3.utils.toWei('0', 'ether') // const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') //What should //const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', diff --git a/scripts/units/add-liquidity-to-xdc-pool.js b/scripts/units/add-liquidity-to-xdc-pool.js index 2e9dfd2..a023b4d 100644 --- a/scripts/units/add-liquidity-to-xdc-pool.js +++ b/scripts/units/add-liquidity-to-xdc-pool.js @@ -6,16 +6,15 @@ const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol" const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const WETH_ADDRESS = addressesExternal.WETH_ADDRESS +const addressesConfig = require('../../config/config.js') +const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') const AMOUNT_ETH = web3.utils.toWei('100', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('50', 'ether') const SLIPPAGE = 0.05 //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -83,7 +82,7 @@ const _encodeAddLiqudityFunction = ( module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); const deadline = await getDeadlineTimestamp(10000) - const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const uniswapRouter = await IUniswapRouter.at(addressesConfig.DEX_ROUTER_ADDRESS) //ReserveA/ReserveB = (ReserveA + QTYA)/(ReserveB + QTYB) //ReserveA*ReserveB + ReserveA*QTYB = ReserveA*ReserveB +ReserveB*QRYA diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 2113ff2..4b103af 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -8,8 +8,8 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../config/config.js') const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ @@ -26,7 +26,7 @@ const Amount_B_Minimum = web3.utils.toWei('1', 'ether') // const Amount_B_Minimum = web3.utils.toWei('9000000', 'ether') //What should //const DEX_ROUTER_ADDRESS = "0xF0392b8A2ea9567dFa900dDb0C2E4296bC061A4C" //SET NEW ROUTER -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 6be7a86..127e0f5 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -4,8 +4,8 @@ const txnHelper = require('./helpers/submitAndExecuteTransaction') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../config/config.js') const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') @@ -14,7 +14,7 @@ const AMOUNT_TOKEN_ETH = web3.utils.toWei('3', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('1', 'ether') //const DEX_ROUTER_ADDRESS = "0x05b0e01DD9737a3c0993de6F57B93253a6C3Ba95"//old router -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 3c77d27..1a18c91 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -10,17 +10,17 @@ const addresses = JSON.parse(rawdata); const rawDataStablecoin = fs.readFileSync('../../config/stablecoin-addresses-proxy-wallet.json'); const addressesStableCoin = JSON.parse(rawDataStablecoin); const XDC_COL = web3.utils.toWei('20','ether') -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../config/config.js') //xdcBe6f6500C3e45a78E17818570b99a7646F8b59F3 const PROXY_WALLET = addressesStableCoin.proxyWallet -const positionMananger = addressesExternal.positionManager -const stabilityFeeCollector = addressesExternal.stabilityFeeCollector -const xdcAdapter = addressesExternal.xdcAdapter -const stablecoinAdapter = addressesExternal.stablecoinAdapter -const collateralPoolId = addressesExternal.collateralPoolId +const positionMananger = addressesConfig.positionManager +const stabilityFeeCollector = addressesConfig.stabilityFeeCollector +const xdcAdapter = addressesConfig.xdcAdapter +const stablecoinAdapter = addressesConfig.stablecoinAdapter +const collateralPoolId = addressesConfig.collateralPoolId const stablecoinAmount = web3.utils.toWei('5') const data = "0x00" diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index 6a59ac2..7175512 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -1,4 +1,6 @@ const fs = require('fs'); +const path = require('path'); + const constants = require('./helpers/constants') const eventsHelper = require("../tests/helpers/eventsHelper"); const txnSaver = require('./helpers/transactionSaver') @@ -7,9 +9,10 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.sol"); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const PROXY_WALLET_REGISTRY_ADDRESS = addressesExternal.PROXY_WALLET_REGISTRY_ADDRESS + +const addressesConfig = require('../../config/config.js') +const PROXY_WALLET_REGISTRY_ADDRESS = addressesConfig.PROXY_WALLET_REGISTRY_ADDRESS + //const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" const _encodeBuildFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ @@ -45,11 +48,22 @@ module.exports = async function(deployer) { } let data = JSON.stringify(addressesStableCoin); - fs.writeFileSync('./config/stablecoin-addresses-proxy-wallet.json',data, function(err){ - if(err){ - console.log(err) - } - }) + const filePath = ('./config/stablecoin-addresses-proxy-wallet.json') + const dirPath = path.dirname(filePath) + + if (fs.existsSync(filePath)) { + rawdata = fs.readFileSync(filePath); + } else if (!fs.existsSync(dirPath)) { + // create new directory + fs.mkdirSync(dirPath, { recursive: true }); + // create new file + fs.writeFileSync(filePath, data); + rawdata = fs.readFileSync(filePath); + } else{ + // create new file + fs.writeFileSync(filePath, data) + rawdata = fs.readFileSync(filePath); + } await txnSaver.saveTxnIndex("proxyWalletTxn", txIndexBuild) } \ No newline at end of file diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index 65fa260..8f44931 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -7,8 +7,8 @@ const constants = require('./helpers/constants') const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../config/config.js') //SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) const value = constants.EMPTY_BYTES; @@ -79,7 +79,7 @@ module.exports = async function(deployer) { _descriptionHash ) => { const result = await multiSigWallet.submitTransaction( - addressesExternal.MAIN_TOKEN_GOVERNOR_ADDRESS, + addressesConfig.MAIN_TOKEN_GOVERNOR_ADDRESS, value, _encodeExecuteProposal( _targets, diff --git a/scripts/units/helpers/constants.js b/scripts/units/helpers/constants.js index 58d348b..fd475cf 100644 --- a/scripts/units/helpers/constants.js +++ b/scripts/units/helpers/constants.js @@ -1,13 +1,11 @@ const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; const PATH_TO_ADDRESSES= '../../addresses.json' -const PATH_TO_ADDRESSES_EXTERNAL = '../../config/external-addresses.json' const PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX = './config/newly-generated-transaction-index.json' module.exports = { SUBMIT_TRANSACTION_EVENT, EMPTY_BYTES, PATH_TO_ADDRESSES, - PATH_TO_ADDRESSES_EXTERNAL, PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX } diff --git a/scripts/units/helpers/transactionSaver.js b/scripts/units/helpers/transactionSaver.js index 62e5fe8..49d5753 100644 --- a/scripts/units/helpers/transactionSaver.js +++ b/scripts/units/helpers/transactionSaver.js @@ -1,12 +1,22 @@ const fs = require('fs'); +const path = require('path'); + +const filePath = '../../../config/newly-generated-transaction-index.json'; +const dirPath = path.dirname(filePath); let rawdata; -if (fs.existsSync('../../../config/newly-generated-transaction-index.json')) { - rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); -} else { +if (fs.existsSync(filePath)) { + rawdata = fs.readFileSync(filePath); +} else if (!fs.existsSync(dirPath)) { + // create new directory + fs.mkdirSync(dirPath, { recursive: true }); + // create new file + fs.writeFileSync(filePath, '{}'); + rawdata = fs.readFileSync(filePath); +} else{ // create new file - fs.writeFileSync('../../../config/newly-generated-transaction-index.json', '{}'); - rawdata = fs.readFileSync('../../../config/newly-generated-transaction-index.json'); + fs.writeFileSync(filePath, '{}'); + rawdata = fs.readFileSync(filePath); } const constants = require('./constants') diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index e43e176..6bb653a 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -5,7 +5,7 @@ const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); -const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); +const addressesConfig = require('../../config/config.js') const LOCK_PERIOD = 365 * 24 * 60 * 60; //SET AS NEEDED @@ -15,9 +15,9 @@ const T_TOTAL_TO_APPROVE = web3.utils.toWei('30000000', 'ether'); // this is how much to stake for one council . Right now 10KK const T_TO_STAKE = web3.utils.toWei('10000000', 'ether'); -const COUNCIL_1 = "0xE82C380C6Ca0306C61454569e84e020d68B063EF"; -const COUNCIL_2 = "0x2B3691065A78F5fb02E9BF54A197b95da2B26AF7"; -const COUNCIL_3 = "0xFa869165D4fB9DB1041eBc3E8D976847372FcF91"; +const COUNCIL_1 = addressesConfig.COUNCIL_1; +const COUNCIL_2 = addressesConfig.COUNCIL_2; +const COUNCIL_3 = addressesConfig.COUNCIL_3; const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); diff --git a/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js index 742918e..0c495e4 100644 --- a/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const BOOK_KEEPER_ADDRESS =addressesConfig.BOOK_KEEPER_ADDRESS const TO_BE_ALLOWLISTED = "0x" const _encodeAllowlist = (toBeAllowlistedAddress) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js index d599314..a4ea501 100644 --- a/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const BOOK_KEEPER_ADDRESS =addressesExternal.BOOK_KEEPER_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const BOOK_KEEPER_ADDRESS =addressesConfig.BOOK_KEEPER_ADDRESS const TO_BE_BLOCKLISTED = "0x" const _encodeBlocklist = (toBeBlocklistedAddress) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js index c12ddb0..44eb65c 100644 --- a/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js +++ b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const COLLATERAL_POOL_ID = '' const DEBT_CEILING = '' const DEBT_FLOOR = '' diff --git a/scripts/units/stablecoin/collateral-pool-config/set-adapter.js b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js index 4c5563e..1ff78b3 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-adapter.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const ADAPTER = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetAdapter = (_collateralPoolId, _adapter) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setAdapter', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js index 8b225cc..791af94 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const CLOSE_FACTOR_BPS = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeCloseFactorBPS = (_collateralPoolId, _closeFactorBps) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setCloseFactorBps', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js index 9d59e1a..4a3d657 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const DEBT_ACCUMULATED_RATE = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _debtAccumulatedRate) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setDebtAccumulatedRate', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js index 09de663..cde405b 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const DEBT_CEILING = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetDebtCeiling = (_collateralPoolId, _debtCeiling) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setDebtCeiling', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js index 48a3d64..7c81907 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const DEBT_FLOOR = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetDebtFloor = (_collateralPoolId, _debtFloor) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setDebtFloor', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js index ac2f242..39a4541 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const DATA = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetLiquidationRatio = (_collateralPoolId, _data) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setLiquidationRatio', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js index 8fab4d6..670c8ee 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const LIQUIDATOR_INCENTIVE_BPS = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _liquidatorIncentiveBps) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setLiquidatorIncentiveBps', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js index 664c4d8..b746fb8 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const PRICE_FEED = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetPriceFeed = (_collateralPoolId, _priceFeed) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setPriceFeed', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js index 63abbd8..9ee5ec5 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const PRICE_WITH_SAFETY_MARGIN = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetPriceWithSafetyMargin = (_collateralPoolId, _priceWithSafetyMargin) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setPriceWithSafetyMargin', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js index 6fb58ea..5a8e009 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const STABILITY_FEE = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeStabilityFeeRate = (_collateralPoolId, _stabilityFeeRate) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setStabilityFeeRate', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-strategy.js b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js index 988fa9a..56be663 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-strategy.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const STRATEGY = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetStrategy = (_collateralPoolId, _strategy) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setStrategy', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js index e800178..0cd3317 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const TOTAL_DEBT_SHARE = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeTotalDebtShare = (_collateralPoolId, _totalDebtShare) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setTotalDebtShare', diff --git a/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js index eadf213..a912ef3 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' const TREASURY_FEES_BPS = '' -const COLLATERAL_POOL_CONFIG_ADDRESS =addressesExternal.COLLATERAL_POOL_CONFIG_ADDRESS +const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeLiquidatorIncentiveBPS = (_collateralPoolId, _treasuryFeesBps) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setTreasuryFeesBps', diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js index f3d6b80..a681c47 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js index cddd37b..4cff8fd 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeUnpause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js index 7c70f00..5cfa6d1 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const ACCESS_CONTROL_CONFIG = "" -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetAccessControlConfig = (_accessControlConfig) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js index bf73e69..d1ef95f 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const ORACLE = "" -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetOracle = (_oracle) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js index e69e0bf..8cb9b2a 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const SECOND = 1 -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetPriceLife = (_second) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js index a232f84..a025c4e 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetPrice = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js index 005499e..bc75871 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const SECOND = 1 -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetTimeDelay = (_second) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js index bdbcd71..0580e8d 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const TOKEN_ONE = "" -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetToken1 = (_token) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js index ae9a010..ab90d82 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const TOKEN_ZERO = "" -const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesExternal.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS +const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS const _encodeSetToken0 = (_token) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js index 5e76c5c..1aee576 100644 --- a/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js +++ b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const FEE_RATE =1 -const FLASH_MINT_MODULE_ADDRESS = addressesExternal.FLASH_MINT_MODULE_ADDRESS +const FLASH_MINT_MODULE_ADDRESS = addressesConfig.FLASH_MINT_MODULE_ADDRESS const _encodeSetFeeRate = (_data) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/position-manager/set-price-oracle.js b/scripts/units/stablecoin/position-manager/set-price-oracle.js index b93303c..91c6431 100644 --- a/scripts/units/stablecoin/position-manager/set-price-oracle.js +++ b/scripts/units/stablecoin/position-manager/set-price-oracle.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const PRICE_ORACLE = "" -const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS +const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS const _encodeSetPriceOracle = (_priceOracle) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/position-manager/stableswap-pause.js b/scripts/units/stablecoin/position-manager/stableswap-pause.js index c1da953..421cc01 100644 --- a/scripts/units/stablecoin/position-manager/stableswap-pause.js +++ b/scripts/units/stablecoin/position-manager/stableswap-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/position-manager/stableswap-unpause.js b/scripts/units/stablecoin/position-manager/stableswap-unpause.js index ba7249e..b3ae5f1 100644 --- a/scripts/units/stablecoin/position-manager/stableswap-unpause.js +++ b/scripts/units/stablecoin/position-manager/stableswap-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const POSITION_MANAGER_ADDRESS =addressesExternal.POSITION_MANAGER_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS const _encodeUnpause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-cage.js b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js index 81a0714..a8ab51a 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-cage.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const _encodeCage = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-pause.js b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js index 6da7971..bb876a2 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-pause.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js index b7cb546..f1d6ec0 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const _encodeUncage = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js index d13e1f4..394795e 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const _encodeUnpause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/set-price.js b/scripts/units/stablecoin/price-oracle/set-price.js index 707ebd2..fc8caba 100644 --- a/scripts/units/stablecoin/price-oracle/set-price.js +++ b/scripts/units/stablecoin/price-oracle/set-price.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const COLLATERAL_POOL_ID = 123 const _encodeSetPrice = (_collateralPoolId) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js index 59e326a..df5ef13 100644 --- a/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js +++ b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PRICE_ORACLE_ADDRESS =addressesExternal.PRICE_ORACLE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const DATA = 123 const _encodeSetStableCoinReferencePrice = (_data) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js index 47bf803..a511a0f 100644 --- a/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js +++ b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const PROXY_ACTION_STORAGE_ADDRESS =addressesExternal.PROXY_ACTION_STORAGE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const PROXY_ACTION_STORAGE_ADDRESS =addressesConfig.PROXY_ACTION_STORAGE_ADDRESS const PROXY_ACTION_ADDRESS = "0x" const _encodeSetProxyAction = (_proxyAction) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/cage-pool.js b/scripts/units/stablecoin/showstopper/cage-pool.js index 6696840..4908832 100644 --- a/scripts/units/stablecoin/showstopper/cage-pool.js +++ b/scripts/units/stablecoin/showstopper/cage-pool.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const COLLATERAL_POOL_ID = '' -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeCagePool = (_collateralPoolId) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/cage-showstopper.js b/scripts/units/stablecoin/showstopper/cage-showstopper.js index 7f1e6ea..1f9f99a 100644 --- a/scripts/units/stablecoin/showstopper/cage-showstopper.js +++ b/scripts/units/stablecoin/showstopper/cage-showstopper.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeCage = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/set-book-keeper.js b/scripts/units/stablecoin/showstopper/set-book-keeper.js index 97bde13..c04fdad 100644 --- a/scripts/units/stablecoin/showstopper/set-book-keeper.js +++ b/scripts/units/stablecoin/showstopper/set-book-keeper.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const BOOK_KEEPER_ADDRESS = "" -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeSetBookKeeper = (_bookkeeper) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/set-cage-cool-down.js b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js index da3efaa..041cfcf 100644 --- a/scripts/units/stablecoin/showstopper/set-cage-cool-down.js +++ b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const CAGE_COOL_DOWN =1 -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeSetCageCooldown = (_cageCoolDown) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/set-liquidation-engine.js b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js index 9df3795..b71d245 100644 --- a/scripts/units/stablecoin/showstopper/set-liquidation-engine.js +++ b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const LIQUIDATION_ENGINE_ADDRESS = "" -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeLiquidationEngine = (_liquidationEngine) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'setLiquidationEngine', diff --git a/scripts/units/stablecoin/showstopper/set-price-oracle.js b/scripts/units/stablecoin/showstopper/set-price-oracle.js index 8ca9061..c100ecf 100644 --- a/scripts/units/stablecoin/showstopper/set-price-oracle.js +++ b/scripts/units/stablecoin/showstopper/set-price-oracle.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const PRICE_ORACLE = "" -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeSetPriceOracle = (_priceOracle) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/showstopper/set-system-debt-engine.js b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js index 6a1cc54..b1161e2 100644 --- a/scripts/units/stablecoin/showstopper/set-system-debt-engine.js +++ b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const SYSTEM_DEBT_ENGINE = "" -const SHOW_STOPPER_ADDRESS =addressesExternal.SHOW_STOPPER_ADDRESS +const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS const _encodeSetSystemDebtEngine = (_systemDebtEngine) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js index 5e666bd..31f4806 100644 --- a/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js +++ b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS const _encodeCollect = (_collateralPool) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js index d1bd97b..4da93c3 100644 --- a/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js +++ b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const SYSTEM_DEBT_ENGINE_ADDRESS = "" -const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS const _encodeSetBookKeeper = (_systemDebtEngine) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js index 6ea7f9e..4928b21 100644 --- a/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js index fc37f21..fec798e 100644 --- a/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const STABILITY_FEE_COLLECTOR_ADDRESS =addressesExternal.STABILITY_FEE_COLLECTOR_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS const _encodeUnpause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/emergency-withdraw.js b/scripts/units/stablecoin/stableswap/emergency-withdraw.js index 9dbf48a..5e94ec9 100644 --- a/scripts/units/stablecoin/stableswap/emergency-withdraw.js +++ b/scripts/units/stablecoin/stableswap/emergency-withdraw.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const ACCOUNT_DESTINATION = "" -const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS +const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS const _encodeEmergencyWithdraw = (_destination) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/set-fee-in.js b/scripts/units/stablecoin/stableswap/set-fee-in.js index 3f6ddfd..91112b8 100644 --- a/scripts/units/stablecoin/stableswap/set-fee-in.js +++ b/scripts/units/stablecoin/stableswap/set-fee-in.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const FEE_IN = 1 -const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS +const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS const _encodeSetFeeIn = (_feeIn) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/set-fee-out.js b/scripts/units/stablecoin/stableswap/set-fee-out.js index fc80419..683cb58 100644 --- a/scripts/units/stablecoin/stableswap/set-fee-out.js +++ b/scripts/units/stablecoin/stableswap/set-fee-out.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const FEE_OUT = 1 -const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS +const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS const _encodeSetFeeIn = (_feeOut) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/stableswap-pause.js b/scripts/units/stablecoin/stableswap/stableswap-pause.js index 262684b..dcd062d 100644 --- a/scripts/units/stablecoin/stableswap/stableswap-pause.js +++ b/scripts/units/stablecoin/stableswap/stableswap-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/stableswap-unpause.js b/scripts/units/stablecoin/stableswap/stableswap-unpause.js index 9b93421..a4cae7b 100644 --- a/scripts/units/stablecoin/stableswap/stableswap-unpause.js +++ b/scripts/units/stablecoin/stableswap/stableswap-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS const _encodeUnpause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/stableswap/withdraw-fees.js b/scripts/units/stablecoin/stableswap/withdraw-fees.js index a76d92f..f1ce7f0 100644 --- a/scripts/units/stablecoin/stableswap/withdraw-fees.js +++ b/scripts/units/stablecoin/stableswap/withdraw-fees.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const _destination = "" -const STABLE_SWAP_ADDRESS =addressesExternal.STABLE_SWAP_ADDRESS +const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS const _encodeWithdrawFees = (_destination) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js index 88a0aa2..040ffc6 100644 --- a/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js +++ b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const DATA = 1 -const SYSTEM_DEBT_ENGINE_ADDRESS = addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS +const SYSTEM_DEBT_ENGINE_ADDRESS = addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS const _encodeSetSurplusBuffer = (_data) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js index 0be08cc..8f921f2 100644 --- a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js +++ b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS const _encodePause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js index ca7decf..98db800 100644 --- a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js +++ b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS +const addressesConfig = require('../../../../config/config.js') + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS const _encodeUnause = () => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js index 62f4e74..bcab14c 100644 --- a/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js +++ b/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js @@ -1,9 +1,9 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); -const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRSESS +const addressesConfig = require('../../../../config/config.js') + +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRSESS const COLLATERAL_POOL_ID = '' const ADAPTER = '' const TO = '' diff --git a/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js index 12cd899..31cc281 100644 --- a/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js +++ b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js @@ -1,10 +1,10 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const rawdataExternal = fs.readFileSync('../../../../config/external-addresses.json'); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../../../config/config.js') const TO = "0x" const VALUE = 123 -const SYSTEM_DEBT_ENGINE_ADDRESS =addressesExternal.SYSTEM_DEBT_ENGINE_ADDRESS +const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS const _encodeWithdrawStablecoinSurplus = (_to,_value) => { let toRet = web3.eth.abi.encodeFunctionCall({ diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js index e50d2b7..b0e75bc 100644 --- a/scripts/units/stableswap-daily-limit-update.js +++ b/scripts/units/stableswap-daily-limit-update.js @@ -3,9 +3,9 @@ const fs = require('fs'); const txnHelper = require('./helpers/submitAndExecuteTransaction') const constants = require('./helpers/constants') -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS + +const addressesConfig = require('../../config/config.js') +const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS //const STABLE_SWAP_ADDRESS = "" //SET const DAILY_LIMIT = web3.utils.toWei('100000','ether') //SET diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index aed2a6a..0baf210 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -8,11 +8,11 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const STABLE_SWAP_ADDRESS = addressesExternal.STABLE_SWAP_ADDRESS -const USDAddress = addressesExternal.USD_ADDRESS -const FXDAddress = addressesExternal.FXD_ADDRESS + +const addressesConfig = require('../../config/config.js') +const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS +const USDAddress = addressesConfig.USD_ADDRESS +const FXDAddress = addressesConfig.FXD_ADDRESS //const STABLE_SWAP_ADDRESS = ""; const USDDepositAmount = web3.utils.toWei('400000','ether') const FXDDepositAmount = web3.utils.toWei('400000','ether') diff --git a/scripts/units/swap-Exact-ETH-For-Tokens.js b/scripts/units/swap-Exact-ETH-For-Tokens.js index eea63e7..2183e2d 100644 --- a/scripts/units/swap-Exact-ETH-For-Tokens.js +++ b/scripts/units/swap-Exact-ETH-For-Tokens.js @@ -8,15 +8,15 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const WETH_ADDRESS = addressesExternal.WETH_ADDRESS + +const addressesConfig = require('../../config/config.js') +const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //WXDC const AMOUNT_IN_ETH = '2' const SLIPPAGE = 0.05 -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeSwapExactETHForTokens = ( _amountOutMin, @@ -57,7 +57,7 @@ const _encodeSwapExactETHForTokens = ( module.exports = async function(deployer) { //we want to swap exact eth for tokens const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const uniswapRouter = await IUniswapRouter.at(addressesConfig.DEX_ROUTER_ADDRESS) //we set path to swap from WETH and receive Token const path = [WETH_ADDRESS, TOKEN_ADDRESS] // we set exact eth that we want to swap diff --git a/scripts/units/swap-Exact-Tokens-For-Tokens.js b/scripts/units/swap-Exact-Tokens-For-Tokens.js index 42fc9bf..fa75342 100644 --- a/scripts/units/swap-Exact-Tokens-For-Tokens.js +++ b/scripts/units/swap-Exact-Tokens-For-Tokens.js @@ -8,8 +8,8 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); + +const addressesConfig = require('../../config/config.js') const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC @@ -17,7 +17,7 @@ const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC const AMOUNT_IN_TOKEN_A = '2' const SLIPPAGE = 0.05 -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -80,7 +80,7 @@ module.exports = async function(deployer) { const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const uniswapRouter = await IUniswapRouter.at(addressesConfig.DEX_ROUTER_ADDRESS) //amounts In is the fixed amount you want to give to the uniswap pool const amountIn = web3.utils.toWei(AMOUNT_IN_TOKEN_A, 'ether') //now, we set path where Token A is swapped to Token B diff --git a/scripts/units/swap-Tokens-For-Exact-ETH.js b/scripts/units/swap-Tokens-For-Exact-ETH.js index b0c0436..0828ba5 100644 --- a/scripts/units/swap-Tokens-For-Exact-ETH.js +++ b/scripts/units/swap-Tokens-For-Exact-ETH.js @@ -8,15 +8,15 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const rawdataExternal = fs.readFileSync(constants.PATH_TO_ADDRESSES_EXTERNAL); -const addressesExternal = JSON.parse(rawdataExternal); -const WETH_ADDRESS = addressesExternal.WETH_ADDRESS + +const addressesConfig = require('../../config/config.js') +const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" const AMOUNT_OUT_ETH = '2' const SLIPPAGE = 0.05 -const DEX_ROUTER_ADDRESS = addressesExternal.DEX_ROUTER_ADDRESS +const DEX_ROUTER_ADDRESS = addressesConfig.DEX_ROUTER_ADDRESS const _encodeApproveFunction = (_account, _amount) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'approve', @@ -78,7 +78,7 @@ const _encodeSwapTokensForExactETH = ( module.exports = async function(deployer) { // we want to swap tokens for exact eth const multiSigWallet = await IMultiSigWallet.at(addresses.multiSigWallet); - const uniswapRouter = await IUniswapRouter.at(addressesExternal.DEX_ROUTER_ADDRESS) + const uniswapRouter = await IUniswapRouter.at(addressesConfig.DEX_ROUTER_ADDRESS) //path to swap Token to Fixed ETH const path = [TOKEN_ADDRESS,WETH_ADDRESS] const deadline = await getDeadlineTimestamp(10000) diff --git a/template/config-template.js b/template/config-template.js new file mode 100644 index 0000000..d11d6b8 --- /dev/null +++ b/template/config-template.js @@ -0,0 +1,24 @@ +module.exports = { + positionManager: "", + stabilityFeeCollector: "", + xdcAdapter: "", + stablecoinAdapter: "", + collateralPoolId: "", + DEX_ROUTER_ADDRESS: "", + PROXY_WALLET_REGISTRY_ADDRESS: "", + STABLE_SWAP_ADDRESS: "", + USD_ADDRESS: "", + FXD_ADDRESS: "", + SHOW_STOPPER_ADDRESS: "", + SYSTEM_DEBT_ENGINE_ADDRESS: "", + STABILIITY_FEE_COLLECTOR_ADDRESS: "", + PRICE_ORACLE_ADDRESS: "", + BOOK_KEEPER_ADDRESS: "", + POSITION_MANAGER_ADDRESS: "", + COLLATERAL_POOL_CONFIG_ADDRESS: "", + DEX_FACTORY_ADDRESS: "", + WETH_ADDRESS: "", + COUNCIL_1:"", + COUNCIL_2:"", + COUNCIL_3:"" +}; From c1ecc86df45b01b475ad5ccdfe84f70b9a1ce765 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 7 Apr 2023 14:22:27 +0545 Subject: [PATCH 49/56] removed config for multisig scripts --- docs/SCENARIOS-INSTRUCTIONS.md | 54 ++++++++++++------- .../migrations/prod/2_add_vault_operator.js | 6 +-- scripts/migrations/prod/5_init_main_stream.js | 6 +-- .../prod/7_send_main_token_to_address.js | 6 +-- .../migrations/test/3_add_vault_operator.js | 6 +-- scripts/migrations/test/6_init_main_stream.js | 5 +- scripts/migrations/test/7_setup_multisig.js | 8 +-- scripts/tests/dao/full-flow-demo.test.js | 5 +- .../dao/governance/multisig-treasury.test.js | 5 +- .../dao/governance/proposal-flow.test.js | 5 +- .../token-creation-and-delegate.test.js | 5 +- .../dao/staking/staking-criticals.test.js | 6 +-- scripts/tests/dao/staking/staking.test.js | 6 +-- scripts/tests/helpers/testConstants.js | 9 ++++ scripts/units/add-liquidity-to-pool.js | 9 ++-- scripts/units/add-liquidity-to-xdc-pool.js | 4 +- scripts/units/create-upgrade.js | 6 +-- scripts/units/create_pool_dex.js | 4 +- scripts/units/create_pool_dex_xdc.js | 2 +- .../units/create_stablecoin_open_position.js | 11 ++-- .../units/create_stablecoin_proxy_wallet.js | 1 - scripts/units/create_stream.js | 2 +- scripts/units/helpers/create-config.js | 47 ++++++++++++++++ .../helpers/submitAndExecuteTransaction.js | 30 +++++++++-- scripts/units/helpers/transactionSaver.js | 5 +- scripts/units/propose_stream.js | 6 +-- scripts/units/setup-multisig-owners.js | 7 +-- .../units/stableswap-daily-limit-update.js | 34 ------------ scripts/units/swap-Exact-ETH-For-Tokens.js | 2 +- scripts/units/swap-Exact-Tokens-For-Tokens.js | 4 +- scripts/units/swap-Tokens-For-Exact-ETH.js | 2 +- scripts/units/transfer-tokens.js | 2 +- template/config-template.js | 24 --------- 33 files changed, 187 insertions(+), 147 deletions(-) create mode 100644 scripts/tests/helpers/testConstants.js create mode 100644 scripts/units/helpers/create-config.js delete mode 100644 scripts/units/stableswap-daily-limit-update.js delete mode 100644 template/config-template.js diff --git a/docs/SCENARIOS-INSTRUCTIONS.md b/docs/SCENARIOS-INSTRUCTIONS.md index d411b9e..437aa81 100644 --- a/docs/SCENARIOS-INSTRUCTIONS.md +++ b/docs/SCENARIOS-INSTRUCTIONS.md @@ -3,8 +3,8 @@ ## Table of contents - [Scenarios Instructions](#scenarios-instructions) - - [How is file Structure](#How-is-file-Structured) - [How to setup at first](#How-to-setup-at-first) + - [How is file Structure](#How-is-file-Structured) - [How to Setup Council Stakes](#How-to-Setup-Council-Stakes) - [How to transfer tokens](#How-to-transfer-tokens) - [How to Add Owners](#How-to-Add-owners) @@ -16,19 +16,23 @@ - [Queue Proposal](#Queue-Proposal) - [Execute Proposal](#Execute-Proposal) -## How is file Structured? - - -In config/config.js file all the addresses are stored that needs to be used externally - -In config/newly-generated-transaction-index.json file all the newly generated transaction indexes are stored. Note: It would be a good practise to make this file empty for each new deployment you do and each new deployment in another branch. - ## How to setup at first: 1. First you need to have addresses.json file in your root folder. This is automatically setup while doing deployment. -2. A config folder needs to be created at root folder. There is a template folder in root folder named config-template.js. Copy the config-template.js and paste it in config directory and rename it with config.js +2. A config folder needs to be created at root folder. To do this simply run the command: + +``` +node scripts/units/helpers/create-config.js +``` +And after creating config.js just fill it with required addresses So basically you need to end up with config/config.js file path in your root folder for the scripts to work +Please be aware that each unit scripts need adjustment as per need and must be checked before performing execution + +## How is file Structured? +In config/config.js file all the addresses are stored that needs to be used externally + +In config/newly-generated-transaction-index.json file all the newly generated transaction indexes are stored. ## How to Setup Council Stakes @@ -36,7 +40,7 @@ So basically you need to end up with config/config.js file path in your root fol In file in scripts/units/setup_council_stakes.js -1. Hardcode COUNCIL_1, COUNCIL_2, COUNCIL_3 +1. The councils need to be updated in the config.js file 2. Hardcode T_TO_STAKE: Lock position for each council 3. Hardcode T_TOTAL_TO_APPROVE: Total to approve. Should be T_TO_STAKE * Number of councils 4. coralX scenario –run addCouncilStakesApothem @@ -50,17 +54,18 @@ Hardcode: ## How to Add owners -1. In scripts/units/setup-multisig-owners.js Hardcode the COUNCIL_1 and COUNCIL_2 -2. coralX scenario --run addOwnersToMultisigApothem +1. In scripts/units/setup-multisig-owners.js +2. COUNCIL_2 and COUNCIL_3 are from config.js +3. coralX scenario --run addOwnersToMultisigApothem ## How to create dex pool with Native Token 1. In scripts/units/create_pool_dex_xdc.js Hardcode the followings: -* TOKEN_ADDRESS +* TOKEN_ADDRESS * AMOUNT_TOKEN_DESIRED * AMOUNT_TOKEN_MIN +* AMOUNT_TOKEN_ETH * AMOUNT_ETH_MIN -* DEX_ROUTER_ADDRESS //this comes from external address -* TOKEN_ETH +* DEX_ROUTER_ADDRESS //this comes from config.js 2. coralX scenario --run createDexXDCPoolApothem @@ -72,14 +77,18 @@ Hardcode: * Amount_B_Desired * Amount_A_Minimum * Amount_B_Minimum -* const DEX_ROUTER_ADDRESS //this comes from external address +* const DEX_ROUTER_ADDRESS //this comes from config.js 2. coralX scenario --run createDexXDCPoolApothem ## How to create Proxy Wallet -#### Note (VIMP): This can be called only once for any address. So, if I call once from multisig, it will create a proxy wallet whose address is stored in 'config/stablecoin-addresses-proxy-wallet.json'. This address is then used to create positions and this address never changes for a particular Multisig or EOA. +#### Note (VIMP): This can be called only once for any address. + +The file : create_stablecoin_proxy_wallet.js + +Calling from multisig, will create a proxy wallet whose address will be stored in newly created 'config/stablecoin-addresses-proxy-wallet.json'. This address is then used to create positions and this address never changes for a particular Multisig or EOA. -1. PROXY_WALLET_REGISTRY_ADDRESS will be taken from external addresses +1. PROXY_WALLET_REGISTRY_ADDRESS will be taken from config.js 2. coralX scenario --run createProxyWalletApothem ## How to Open Position @@ -124,3 +133,12 @@ Hardcode: const DESCRIPTION_HASH = ''//this is bytes32 2. coralX scenario --run proposeProposalApothem + +Note: There are other scripts as well. +Note: This script can be made that it only submits a transaction and not really execute it. But for that some changes will be required in submitAndExecuteTransaction.js file. + +Where in the function _submitAndExecute + + await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + +And then the transaction index saved must be remembered for other owners to sign it if needed. diff --git a/scripts/migrations/prod/2_add_vault_operator.js b/scripts/migrations/prod/2_add_vault_operator.js index b2c45cf..07abba3 100644 --- a/scripts/migrations/prod/2_add_vault_operator.js +++ b/scripts/migrations/prod/2_add_vault_operator.js @@ -1,10 +1,10 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); - +const constants = require("../../tests/helpers/testConstants"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const VaultProxy = artifacts.require('./common/proxy/VaultProxy.sol') const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') diff --git a/scripts/migrations/prod/5_init_main_stream.js b/scripts/migrations/prod/5_init_main_stream.js index e56a168..72ffbe8 100644 --- a/scripts/migrations/prod/5_init_main_stream.js +++ b/scripts/migrations/prod/5_init_main_stream.js @@ -1,11 +1,11 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); - +const constants = require("../../tests/helpers/testConstants"); const MainToken = artifacts.require("./dao/tokens/MainToken.sol"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const EXECUTE_TRANSACTION_EVENT = "ExecuteTransaction(address,uint256)"; const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') diff --git a/scripts/migrations/prod/7_send_main_token_to_address.js b/scripts/migrations/prod/7_send_main_token_to_address.js index 7e52354..5d1a342 100644 --- a/scripts/migrations/prod/7_send_main_token_to_address.js +++ b/scripts/migrations/prod/7_send_main_token_to_address.js @@ -1,13 +1,13 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); const MainToken = artifacts.require("./dao/tokens/MainToken.sol"); +const constants = require("../../tests/helpers/testConstants"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; - +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const _encodeTransferFunction = (_account, t_to_stake) => { diff --git a/scripts/migrations/test/3_add_vault_operator.js b/scripts/migrations/test/3_add_vault_operator.js index b2c45cf..07abba3 100644 --- a/scripts/migrations/test/3_add_vault_operator.js +++ b/scripts/migrations/test/3_add_vault_operator.js @@ -1,10 +1,10 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); - +const constants = require("../../tests/helpers/testConstants"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const VaultProxy = artifacts.require('./common/proxy/VaultProxy.sol') const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') diff --git a/scripts/migrations/test/6_init_main_stream.js b/scripts/migrations/test/6_init_main_stream.js index ec95a52..5978078 100644 --- a/scripts/migrations/test/6_init_main_stream.js +++ b/scripts/migrations/test/6_init_main_stream.js @@ -1,9 +1,10 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); +const constants = require("../../tests/helpers/testConstants"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const StakingProxy = artifacts.require('./common/proxy/StakingProxy.sol') const blockchain = require("../../tests/helpers/blockchain"); const MainToken = artifacts.require("./dao/tokens/MainToken.sol"); diff --git a/scripts/migrations/test/7_setup_multisig.js b/scripts/migrations/test/7_setup_multisig.js index bc631a4..2ef1aaa 100644 --- a/scripts/migrations/test/7_setup_multisig.js +++ b/scripts/migrations/test/7_setup_multisig.js @@ -1,12 +1,12 @@ const eventsHelper = require("../../tests/helpers/eventsHelper"); - +const constants = require("../../tests/helpers/testConstants"); const MultiSigWallet = artifacts.require("./dao/treasury/MultiSigWallet.sol"); const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = constants.EMPTY_BYTES; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT -const COUNCIL_1 = "0xE82C380C6Ca0306C61454569e84e020d68B063EF"; +const COUNCIL_1 = constants.COUNCIL_1 const NEW_MULTISIG_REQUIREMENT = 2; diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index 1a021df..67e0250 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -35,6 +35,7 @@ TO INCLUDE: const blockchain = require("../helpers/blockchain"); const eventsHelper = require("../helpers/eventsHelper"); +const constants = require("../helpers/testConstants"); const { assert } = require("chai"); const { shouldRevert, @@ -42,7 +43,7 @@ const { shouldRevertAndHaveSubstring } = require('../helpers/expectThrow'); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; // Proposal 1 @@ -54,7 +55,7 @@ const PROPOSAL_DESCRIPTION_2 = "Proposal #2: Distribute funds from treasury to a // Events const PROPOSAL_CREATED_EVENT = "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)" -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const SYSTEM_ACC = accounts[0]; diff --git a/scripts/tests/dao/governance/multisig-treasury.test.js b/scripts/tests/dao/governance/multisig-treasury.test.js index 29b1e98..ade29a5 100644 --- a/scripts/tests/dao/governance/multisig-treasury.test.js +++ b/scripts/tests/dao/governance/multisig-treasury.test.js @@ -1,5 +1,6 @@ const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); +const constants = require("../../helpers/testConstants"); const { shouldRevert, errTypes, @@ -7,9 +8,9 @@ const { } = require('../../helpers/expectThrow'); // constants -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; // event -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT // Token variables const AMOUNT_OUT_TREASURY = "1000"; diff --git a/scripts/tests/dao/governance/proposal-flow.test.js b/scripts/tests/dao/governance/proposal-flow.test.js index 67d5de5..9a68def 100644 --- a/scripts/tests/dao/governance/proposal-flow.test.js +++ b/scripts/tests/dao/governance/proposal-flow.test.js @@ -1,5 +1,6 @@ const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); +const constants = require("../../helpers/testConstants"); const { assert } = require("chai"); const { shouldRevert, @@ -7,7 +8,7 @@ const { shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; const TRUE_EVENT_RETURN_IN_HEX = "0x0000000000000000000000000000000000000000000000000000000000000001" // Proposal 1 const PROPOSAL_DESCRIPTION = "Proposal #1: Store 1 in the Box contract"; @@ -20,7 +21,7 @@ const SUCCEEDED_PROPOSAL_STATE = "4" const DEFEATED_PROPOSAL_STATE = "3" // Events const PROPOSAL_CREATED_EVENT = "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)" -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const EXECUTE_TRANSACTION_EVENT = "ExecuteTransaction(address,bool,bytes)"; const _encodeConfirmation = async (_proposalId) => { diff --git a/scripts/tests/dao/governance/token-creation-and-delegate.test.js b/scripts/tests/dao/governance/token-creation-and-delegate.test.js index 0bc5713..52255c6 100644 --- a/scripts/tests/dao/governance/token-creation-and-delegate.test.js +++ b/scripts/tests/dao/governance/token-creation-and-delegate.test.js @@ -1,13 +1,14 @@ const { web3 } = require("@openzeppelin/test-helpers/src/setup"); const blockchain = require("../../helpers/blockchain"); const eventsHelper = require("../../helpers/eventsHelper"); +const constants = require("../../helpers/testConstants"); const { shouldRevert, errTypes, shouldRevertAndHaveSubstring } = require('../../helpers/expectThrow'); -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; // Proposal 1 @@ -15,7 +16,7 @@ const PROPOSAL_DESCRIPTION = "Proposal #1: Store 1 in the erc20Factory contract" // Events const PROPOSAL_CREATED_EVENT = "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)" -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const _encodeConfirmation = async (_proposalId) => { diff --git a/scripts/tests/dao/staking/staking-criticals.test.js b/scripts/tests/dao/staking/staking-criticals.test.js index 25f0019..437a38e 100644 --- a/scripts/tests/dao/staking/staking-criticals.test.js +++ b/scripts/tests/dao/staking/staking-criticals.test.js @@ -4,7 +4,7 @@ const chai = require("chai"); const { expect } = chai.use(require('chai-bn')(BN)); const eventsHelper = require("../../helpers/eventsHelper"); const blockchain = require("../../helpers/blockchain"); - +const constants = require("../../helpers/testConstants"); const maxGasForTxn = 600000 const { @@ -24,9 +24,9 @@ const stream_manager = accounts[7]; const stream_rewarder_1 = accounts[8]; const stream_rewarder_2 = accounts[9]; const percentToTreasury = 50; -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; // event -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const _getTimeStamp = async () => { diff --git a/scripts/tests/dao/staking/staking.test.js b/scripts/tests/dao/staking/staking.test.js index d25c27f..cbabad2 100644 --- a/scripts/tests/dao/staking/staking.test.js +++ b/scripts/tests/dao/staking/staking.test.js @@ -4,7 +4,7 @@ const chai = require("chai"); const { expect } = chai.use(require('chai-bn')(BN)); const eventsHelper = require("../../helpers/eventsHelper"); const blockchain = require("../../helpers/blockchain"); - +const constants = require("../../helpers/testConstants"); const maxGasForTxn = 600000 const { @@ -37,9 +37,9 @@ const _createVoteWeights = ( } } -const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const EMPTY_BYTES = constants.EMPTY_BYTES; // event -const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const SUBMIT_TRANSACTION_EVENT = constants.SUBMIT_TRANSACTION_EVENT const _createWeightObject = ( maxWeightShares, diff --git a/scripts/tests/helpers/testConstants.js b/scripts/tests/helpers/testConstants.js new file mode 100644 index 0000000..b7b2a48 --- /dev/null +++ b/scripts/tests/helpers/testConstants.js @@ -0,0 +1,9 @@ +const SUBMIT_TRANSACTION_EVENT = "SubmitTransaction(uint256,address,address,uint256,bytes)"; +const EMPTY_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const COUNCIL_1 = "0xE82C380C6Ca0306C61454569e84e020d68B063EF"; + +module.exports = { + SUBMIT_TRANSACTION_EVENT, + EMPTY_BYTES, + COUNCIL_1 +} diff --git a/scripts/units/add-liquidity-to-pool.js b/scripts/units/add-liquidity-to-pool.js index a6f7685..62a9cb5 100644 --- a/scripts/units/add-liquidity-to-pool.js +++ b/scripts/units/add-liquidity-to-pool.js @@ -10,11 +10,10 @@ const addresses = JSON.parse(rawdata); const addressesConfig = require('../../config/config.js') -const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ -const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC -// SET AS Necessary -const Amount_A_Desired = web3.utils.toWei('2', 'ether') -const Amount_A_Minimum = web3.utils.toWei('0', 'ether') +const Token_A_Address = addressesConfig.WETH_ADDRESS // SET AS Necessary +const Token_B_Address = addressesConfig.FXD_ADDRESS// SET AS Necessary +const Amount_A_Desired = web3.utils.toWei('2', 'ether')// SET AS Necessary +const Amount_A_Minimum = web3.utils.toWei('0', 'ether')// SET AS Necessary // const Amount_A_Desired = web3.utils.toWei('250000', 'ether') // const Amount_B_Desired = web3.utils.toWei('9347335', 'ether') diff --git a/scripts/units/add-liquidity-to-xdc-pool.js b/scripts/units/add-liquidity-to-xdc-pool.js index a023b4d..be49d7f 100644 --- a/scripts/units/add-liquidity-to-xdc-pool.js +++ b/scripts/units/add-liquidity-to-xdc-pool.js @@ -8,8 +8,8 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const addressesConfig = require('../../config/config.js') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS -const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address -const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') +const TOKEN_ADDRESS = addresses.fthmToken //FTHM address + const AMOUNT_ETH = web3.utils.toWei('100', 'ether') const AMOUNT_ETH_MIN = web3.utils.toWei('50', 'ether') const SLIPPAGE = 0.05 diff --git a/scripts/units/create-upgrade.js b/scripts/units/create-upgrade.js index 9f625f9..b445716 100644 --- a/scripts/units/create-upgrade.js +++ b/scripts/units/create-upgrade.js @@ -10,9 +10,9 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); //RIGHT NOW SETUP FOR STAKING -const PROXY_ADMIN = "0xB7a8f3A8178B21499b56d9d054119821953d2C3f" -const PROXY = "0xFD21E72b63568942E541284D275ce1057e7F1257" -const IMPLEMENTATION_ADDRESS = "0xa5B675dd61c00C41F3FA5b919b7E917A61dbE7f7" +const PROXY_ADMIN = "0xB7a8f3A8178B21499b56d9d054119821953d2C3f"//SET AS NECESSARY +const PROXY = "0xFD21E72b63568942E541284D275ce1057e7F1257"//SET AS NECESSARY +const IMPLEMENTATION_ADDRESS = "0xa5B675dd61c00C41F3FA5b919b7E917A61dbE7f7"//SET AS NECESSARY const _encodeUpgradeFunction = (_proxy, _impl) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'upgrade', diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 4b103af..9b2351a 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -12,8 +12,8 @@ const addresses = JSON.parse(rawdata); const addressesConfig = require('../../config/config.js') -const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ -const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC +const Token_A_Address = addressesConfig.USD_ADDRESS //USD +const Token_B_Address = addressesConfig.WETH_ADDRESS //WXDC // SET AS Necessary const Amount_A_Desired = web3.utils.toWei('2', 'ether') const Amount_B_Desired = web3.utils.toWei('38', 'ether') diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 127e0f5..0bcc63b 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -7,7 +7,7 @@ const addresses = JSON.parse(rawdata); const addressesConfig = require('../../config/config.js') -const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //FTHM address +const TOKEN_ADDRESS = addresses.fthmToken //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') const AMOUNT_TOKEN_MIN = web3.utils.toWei('0', 'ether') const AMOUNT_TOKEN_ETH = web3.utils.toWei('3', 'ether') diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 1a18c91..4b96ec3 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -9,17 +9,16 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const rawDataStablecoin = fs.readFileSync('../../config/stablecoin-addresses-proxy-wallet.json'); const addressesStableCoin = JSON.parse(rawDataStablecoin); -const XDC_COL = web3.utils.toWei('20','ether') +const XDC_COL = web3.utils.toWei('20','ether') const addressesConfig = require('../../config/config.js') -//xdcBe6f6500C3e45a78E17818570b99a7646F8b59F3 const PROXY_WALLET = addressesStableCoin.proxyWallet -const positionMananger = addressesConfig.positionManager -const stabilityFeeCollector = addressesConfig.stabilityFeeCollector -const xdcAdapter = addressesConfig.xdcAdapter -const stablecoinAdapter = addressesConfig.stablecoinAdapter +const positionMananger = addressesConfig.POSITION_MANAGER_ADDRESS +const stabilityFeeCollector = addressesConfig.STABILIITY_FEE_COLLECTOR_ADDRESS +const xdcAdapter = addressesConfig.COLLATERAL_TOKEN_ADAPTER_ADDRESS +const stablecoinAdapter = addressesConfig.STABLE_COIN_ADAPTER_ADDRESS const collateralPoolId = addressesConfig.collateralPoolId const stablecoinAmount = web3.utils.toWei('5') diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index 7175512..87059eb 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -13,7 +13,6 @@ const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.s const addressesConfig = require('../../config/config.js') const PROXY_WALLET_REGISTRY_ADDRESS = addressesConfig.PROXY_WALLET_REGISTRY_ADDRESS -//const PROXY_WALLET_REGISTRY_ADDRESS = "0xF11994FBAa365070ce4a1505f9Ce5Ea960d0d904" const _encodeBuildFunction = (_account) => { let toRet = web3.eth.abi.encodeFunctionCall({ name: 'build', diff --git a/scripts/units/create_stream.js b/scripts/units/create_stream.js index ec37cdc..b3d7142 100644 --- a/scripts/units/create_stream.js +++ b/scripts/units/create_stream.js @@ -5,7 +5,7 @@ const txnHelper = require('./helpers/submitAndExecuteTransaction') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const STREAM_REWARD_TOKEN_ADDRESS = "" +const STREAM_REWARD_TOKEN_ADDRESS = addresses.fthmToken //FTHM address //SET AS NECESSARY const REWARD_PROPOSAL_AMOUNT = web3.utils.toWei('','ether') const STREAM_ID = 1//SET const _encodeApproveFunction = (_account, _amount) => { diff --git a/scripts/units/helpers/create-config.js b/scripts/units/helpers/create-config.js new file mode 100644 index 0000000..50d16bf --- /dev/null +++ b/scripts/units/helpers/create-config.js @@ -0,0 +1,47 @@ +const fs = require('fs'); +const path = require('path'); + +const configDir = path.join(path.resolve(), 'config'); +const configPath = path.join(configDir, 'config.js') + +if (fs.existsSync(configPath)) { + console.log(`config.js file already exists in ${configDir} directory!`); +} +else +{ + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir); + } + + const configPath = path.join(configDir, 'config.js'); + + const constants = `module.exports = { + POSITION_MANAGER_ADDRESS: "", + STABILITY_FEE_COLLECTOR_ADDRESS: "", + COLLATERAL_TOKEN_ADAPTER_ADDRESS: "", + STABLE_COIN_ADAPTER_ADDRESS: "", + collateralPoolId: "", + DEX_ROUTER_ADDRESS: "", + PROXY_WALLET_REGISTRY_ADDRESS: "", + STABLE_SWAP_ADDRESS: "", + USD_ADDRESS: "", + FXD_ADDRESS: "", + SHOW_STOPPER_ADDRESS: "", + SYSTEM_DEBT_ENGINE_ADDRESS: "", + STABILIITY_FEE_COLLECTOR_ADDRESS: "", + PRICE_ORACLE_ADDRESS: "", + BOOK_KEEPER_ADDRESS: "", + POSITION_MANAGER_ADDRESS: "", + COLLATERAL_POOL_CONFIG_ADDRESS: "", + DEX_FACTORY_ADDRESS: "", + WETH_ADDRESS: "", + COUNCIL_1:"", + COUNCIL_2:"", + COUNCIL_3:"" + };`; + + fs.writeFile(configPath, constants, (err) => { + if (err) throw err; + console.log(`config.js file has been created in ${configDir} directory!`); + }); +} diff --git a/scripts/units/helpers/submitAndExecuteTransaction.js b/scripts/units/helpers/submitAndExecuteTransaction.js index d61f575..83fb146 100644 --- a/scripts/units/helpers/submitAndExecuteTransaction.js +++ b/scripts/units/helpers/submitAndExecuteTransaction.js @@ -9,19 +9,41 @@ const rawdata = fs.readFileSync('../../../addresses.json'); const addresses = JSON.parse(rawdata); async function submitAndExecute(encodedFunction, targetAddress, TransactionName, ETH_AMOUNT=0) { + const MULTISIG_WALLET_ADDRESS = addresses.multiSigWallet; const multiSigWallet = await IMultiSigWallet.at(MULTISIG_WALLET_ADDRESS); const _submitAndExecute = async() => { - const result = await multiSigWallet.submitTransaction( + const resultSubmitTransaction = await multiSigWallet.submitTransaction( targetAddress, ETH_AMOUNT==0?constants.EMPTY_BYTES:ETH_AMOUNT, encodedFunction ,0,{gas:8000000} ) - const tx = eventsHelper.getIndexedEventArgs(result, constants.SUBMIT_TRANSACTION_EVENT)[0]; - await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); - await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + if (!resultSubmitTransaction) { + console.log(`Transaction failed to submit for ${TransactionName}`); + return; + } else { + console.log(`Transaction submitted successfully for ${TransactionName}. TxHash: ${resultSubmitTransaction.transactionHash}`); + } + + const tx = eventsHelper.getIndexedEventArgs(resultSubmitTransaction, constants.SUBMIT_TRANSACTION_EVENT)[0]; + const resultConfirmTransaction =await multiSigWallet.confirmTransaction(tx, {gas: 8000000}); + if (!resultConfirmTransaction) { + console.log(`Transaction failed to confirm for ${TransactionName}`); + return; + } else { + console.log(`Transaction confirmed successfully for ${TransactionName}. TxHash: ${resultConfirmTransaction.transactionHash}`); + } + + const resultExecuteTransaction = await multiSigWallet.executeTransaction(tx, {gas: 8000000}); + if (!resultExecuteTransaction) { + console.log(`Transaction failed to execute for ${TransactionName}`); + return; + } else { + console.log(`Transaction executed successfully for ${TransactionName}. TxHash: ${resultExecuteTransaction.transactionHash}`); + } + await txnSaver.saveTxnIndex(TransactionName,tx) } diff --git a/scripts/units/helpers/transactionSaver.js b/scripts/units/helpers/transactionSaver.js index 49d5753..1b0a643 100644 --- a/scripts/units/helpers/transactionSaver.js +++ b/scripts/units/helpers/transactionSaver.js @@ -27,7 +27,7 @@ async function saveTxnIndex( ) { let newTxnStore; - + console.log("Transaction Saving : .................", TransactionName) if(rawdata.length <=0){ //if no data present just create a new object and have idx as 1 let object = {} @@ -66,9 +66,8 @@ async function saveTxnIndex( fs.writeFileSync(constants.PATH_TO_NEWLY_GENERATED_TRANSACTION_INDEX,data, function(err){ if(err){ console.log(err) - } + } }) - } module.exports = { diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index 76d4701..b1e810f 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -1,15 +1,15 @@ const fs = require('fs'); const txnSaver = require('./helpers/transactionSaver') const txnHelper = require('./helpers/submitAndExecuteTransaction') +const addressesConfig = require('../../config/config.js') const eventsHelper = require("../tests/helpers/eventsHelper"); const constants = require('./helpers/constants') - const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWallet.sol"); const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const REWARD_TOKEN_ADDRESS = "" -const STREAM_OWNER = "" +const REWARD_TOKEN_ADDRESS = addresses.fthmToken //set as needed +const STREAM_OWNER = addressesConfig.COUNCIL_1 //set as needed const MAX_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') const MIN_DEPOSIT_AMOUNT = web3.utils.toWei('','ether') const PERCENT_TO_TREASURY = 0 diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 9e6a94b..763020f 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -2,12 +2,13 @@ const fs = require('fs'); const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') +const addressesConfig = require('../../config/config.js') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const COUNCIL_1_PLACEHOLDER = "0xc0Ee98ac1a44B56fbe2669A3B3C006DEB6fDd0f9"; -const COUNCIL_2_PLACEHOLDER = "0x01d2D3da7a42F64e7Dc6Ae405F169836556adC86"; +const COUNCIL_2_PLACEHOLDER = addressesConfig.COUNCIL_2; +const COUNCIL_3_PLACEHOLDER = addressesConfig.COUNCIL_2; const _encodeAddOwnersFunction = (_accounts) => { @@ -27,7 +28,7 @@ const _encodeAddOwnersFunction = (_accounts) => { module.exports = async function(deployer) { await txnHelper.submitAndExecute( - _encodeAddOwnersFunction([COUNCIL_1_PLACEHOLDER, COUNCIL_2_PLACEHOLDER]), + _encodeAddOwnersFunction([COUNCIL_2_PLACEHOLDER, COUNCIL_3_PLACEHOLDER]), addresses.multiSigWallet, "setupMultisigOwner" ) diff --git a/scripts/units/stableswap-daily-limit-update.js b/scripts/units/stableswap-daily-limit-update.js deleted file mode 100644 index b0e75bc..0000000 --- a/scripts/units/stableswap-daily-limit-update.js +++ /dev/null @@ -1,34 +0,0 @@ -const fs = require('fs'); - -const txnHelper = require('./helpers/submitAndExecuteTransaction') -const constants = require('./helpers/constants') - - -const addressesConfig = require('../../config/config.js') -const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS -//const STABLE_SWAP_ADDRESS = "" //SET -const DAILY_LIMIT = web3.utils.toWei('100000','ether') //SET - - -const _encodeUpdateDailySwapLimit = (newdailySwapLimit) =>{ - let toRet = web3.eth.abi.encodeFunctionCall({ - name: 'setDailySwapLimit', - type: 'function', - inputs: [{ - type: 'uint256', - name: 'newdailySwapLimit' - }] - }, [newdailySwapLimit]); - - return toRet; -} - -module.exports = async function(deployer) { - await txnHelper.submitAndExecute( - _encodeUpdateDailySwapLimit( - DAILY_LIMIT - ), - STABLE_SWAP_ADDRESS, - "stableSwapDailyLimitUpdate" - ) -} \ No newline at end of file diff --git a/scripts/units/swap-Exact-ETH-For-Tokens.js b/scripts/units/swap-Exact-ETH-For-Tokens.js index 2183e2d..8231ba5 100644 --- a/scripts/units/swap-Exact-ETH-For-Tokens.js +++ b/scripts/units/swap-Exact-ETH-For-Tokens.js @@ -11,7 +11,7 @@ const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol" const addressesConfig = require('../../config/config.js') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS -const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" //WXDC +const TOKEN_ADDRESS = addresses.fthmToken //fthm const AMOUNT_IN_ETH = '2' const SLIPPAGE = 0.05 diff --git a/scripts/units/swap-Exact-Tokens-For-Tokens.js b/scripts/units/swap-Exact-Tokens-For-Tokens.js index fa75342..e919b09 100644 --- a/scripts/units/swap-Exact-Tokens-For-Tokens.js +++ b/scripts/units/swap-Exact-Tokens-For-Tokens.js @@ -11,8 +11,8 @@ const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol" const addressesConfig = require('../../config/config.js') -const Token_A_Address = "0x82b4334F5CD8385f55969BAE0A863a0C6eA9F63f" //USD+ -const Token_B_Address = "0xE99500AB4A413164DA49Af83B9824749059b46ce" //WXDC +const Token_A_Address = addressesConfig.USD_ADDRESS //USD+ +const Token_B_Address = addressesConfig.WETH_ADDRESS //WXDC const AMOUNT_IN_TOKEN_A = '2' const SLIPPAGE = 0.05 diff --git a/scripts/units/swap-Tokens-For-Exact-ETH.js b/scripts/units/swap-Tokens-For-Exact-ETH.js index 0828ba5..fe86683 100644 --- a/scripts/units/swap-Tokens-For-Exact-ETH.js +++ b/scripts/units/swap-Tokens-For-Exact-ETH.js @@ -11,7 +11,7 @@ const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol" const addressesConfig = require('../../config/config.js') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS -const TOKEN_ADDRESS = "0x3f680943866a8b6DBb61b4712c27AF736BD2fE9A" +const TOKEN_ADDRESS = addressesConfig.USD_ADDRESS const AMOUNT_OUT_ETH = '2' const SLIPPAGE = 0.05 diff --git a/scripts/units/transfer-tokens.js b/scripts/units/transfer-tokens.js index 93ab7b6..d963300 100644 --- a/scripts/units/transfer-tokens.js +++ b/scripts/units/transfer-tokens.js @@ -2,7 +2,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const T_TO_TRANSFER_PLACEHOLDER = web3.utils.toWei('10000000','ether') //SET AS NEEDED -const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x746a59A8F41DdC954542B6697954a94868126885" //SET AS NEEDED +const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0xd32Cd592c5296e893AfF7eb8518977A67e4b6741" //SET AS NEEDED const txnHelper = require('./helpers/submitAndExecuteTransaction') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); diff --git a/template/config-template.js b/template/config-template.js deleted file mode 100644 index d11d6b8..0000000 --- a/template/config-template.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = { - positionManager: "", - stabilityFeeCollector: "", - xdcAdapter: "", - stablecoinAdapter: "", - collateralPoolId: "", - DEX_ROUTER_ADDRESS: "", - PROXY_WALLET_REGISTRY_ADDRESS: "", - STABLE_SWAP_ADDRESS: "", - USD_ADDRESS: "", - FXD_ADDRESS: "", - SHOW_STOPPER_ADDRESS: "", - SYSTEM_DEBT_ENGINE_ADDRESS: "", - STABILIITY_FEE_COLLECTOR_ADDRESS: "", - PRICE_ORACLE_ADDRESS: "", - BOOK_KEEPER_ADDRESS: "", - POSITION_MANAGER_ADDRESS: "", - COLLATERAL_POOL_CONFIG_ADDRESS: "", - DEX_FACTORY_ADDRESS: "", - WETH_ADDRESS: "", - COUNCIL_1:"", - COUNCIL_2:"", - COUNCIL_3:"" -}; From 7cffe008f98403eb11440080d6cdf43d8e571345 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 13 Apr 2023 13:53:41 +0545 Subject: [PATCH 50/56] admin can make a lock position without early withdrawal on others behalf --- contracts/dao/staking/StakingStorage.sol | 2 - .../staking/interfaces/IStakingHandler.sol | 4 +- .../dao/staking/packages/StakingHandler.sol | 23 ++--- scripts/tests/dao/full-flow-demo.test.js | 89 +++++++++++-------- .../create-fixed-lock-on-behalf-of-user.js | 81 +++++++++++++++++ scripts/units/setup_council_stakes.js | 29 ++++-- 6 files changed, 163 insertions(+), 65 deletions(-) create mode 100644 scripts/units/create-fixed-lock-on-behalf-of-user.js diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index 3acc935..585a062 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -15,7 +15,6 @@ contract StakingStorage { uint32 internal constant ONE_YEAR = 31536000; uint32 internal constant ONE_DAY = 86400; uint32 internal constant REWARDS_TO_TREASURY_DENOMINATOR = 10000; - //MAX_LOCK: It is a constant. One WEEK Added as a tolerance. uint256 public maxLockPeriod; ///@notice Checks if the staking is initialized @@ -44,7 +43,6 @@ contract StakingStorage { address public voteToken; address public vault; address public rewardsCalculator; - bool internal councilsInitialized; address public treasury; bool internal mainStreamInitialized; diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index 4ab824c..fea646a 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -61,9 +61,7 @@ interface IStakingHandler { function emergencyUnlockAndWithdraw() external; - function createLocksForCouncils(CreateLockParams[] calldata lockParams) external; - - function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) external; + function createFixedLockOnBehalfOfUserByAdmin(CreateLockParams calldata lockPosition) external; function setMinimumLockPeriod(uint256 _minLockPeriod) external; diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 42df4aa..e381b5b 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -280,30 +280,21 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A } /** - * @dev Creating locks for council can be done by Admin only which is Multisig. - * Multisig can create locks for councils + * @dev Admin can create a lock on behalf of a user without early withdrawal */ - function createLocksForCouncils(CreateLockParams[] calldata lockParams) external override onlyRole(DEFAULT_ADMIN_ROLE) { - if (councilsInitialized == true) { - revert AlreadyInitialized(); - } - councilsInitialized = true; - for (uint256 i; i < lockParams.length; i++) { - address account = lockParams[i].account; - prohibitedEarlyWithdraw[account][locks[account].length + 1] = true; - _createLock(lockParams[i].amount, lockParams[i].lockPeriod, account); + function createFixedLockOnBehalfOfUserByAdmin(CreateLockParams calldata lockPosition) external override onlyRole(DEFAULT_ADMIN_ROLE) { + address account = lockPosition.account; + if(account == address(0)){ + revert ZeroAddress(); } + prohibitedEarlyWithdraw[account][locks[account].length + 1] = true; + _createLock(lockPosition.amount, lockPosition.lockPeriod, lockPosition.account); } function createLock(uint256 amount, uint256 lockPeriod) external override pausable(1) { _createLock(amount, lockPeriod, msg.sender); } - function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) external override pausable(1) { - prohibitedEarlyWithdraw[msg.sender][locks[msg.sender].length + 1] = true; - _createLock(amount, lockPeriod, msg.sender); - } - function unlock(uint256 lockId) external override pausable(1) { _verifyUnlock(lockId); LockedBalance storage lock = locks[msg.sender][lockId - 1]; diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index 67e0250..7c6b3be 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -147,14 +147,14 @@ const _encodeTransferFunction = (_account, t_to_stake) => { return toRet; } -const _encodeStakeFunction = (_createLockParam) => { +const _encodeCreateLockWithoutEarlyWithdrawal = (_createLockParam) => { // encoded transfer function call for staking on behalf of someone else from treasury. let toRet = web3.eth.abi.encodeFunctionCall({ - name:'createLocksForCouncils', + name:'createFixedLockOnBehalfOfUserByAdmin', type:'function', inputs: [{ - type: 'tuple[]', - name: 'CreateLockParams', + type: 'tuple', + name: 'lockPosition', components: [ {"type":"uint256", "name":"amount"}, {"type":"uint256", "name":"lockPeriod"}, @@ -966,11 +966,8 @@ describe("DAO Demo", () => { describe('Create lock possitions from treasury on behalf of comity ', async() => { - it('Create multiSig transactions to stake on behalf of comity', async() => { - - const oneYr = 365 * 24 * 60 * 60; - const amount = web3.utils.toWei('200000', 'ether'); - const approveAmount = web3.utils.toWei('400001', 'ether'); + it('Approve FATHOM tokens to staking contracy', async() => { + const approveAmount = web3.utils.toWei('400000', 'ether'); result = await multiSigWallet.submitTransaction( FTHMToken.address, @@ -980,35 +977,46 @@ describe("DAO Demo", () => { {"from": accounts[0]} ); txIndex3 = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; - const LockParamObjectForAllCouncils = [ - _createLockParamObject(amount,oneYr,comity_1), - _createLockParamObject(amount,oneYr,comity_2) - ] - - let result2 = await multiSigWallet.submitTransaction( - stakingService.address, - EMPTY_BYTES, - _encodeStakeFunction(LockParamObjectForAllCouncils), - 0, - {"from": accounts[0]} - ); - txIndex5 = eventsHelper.getIndexedEventArgs(result2, SUBMIT_TRANSACTION_EVENT)[0]; - }) - - it('Confirm and execute multiSig transactions to stake on behalf of comity', async() => { - // Confirm await multiSigWallet.confirmTransaction(txIndex3, {"from": accounts[0]}); await multiSigWallet.confirmTransaction(txIndex3, {"from": accounts[1]}); + await multiSigWallet.executeTransaction(txIndex3, {"from": accounts[0]}); + }) - await multiSigWallet.confirmTransaction(txIndex5, {"from": accounts[0]}); - await multiSigWallet.confirmTransaction(txIndex5, {"from": accounts[1]}); + it('Create multiSig transactions to stake on behalf of comity', async() => { - // execute: - await multiSigWallet.executeTransaction(txIndex3, {"from": accounts[0]}); - await blockchain.increaseTime(20); - await multiSigWallet.executeTransaction(txIndex5, {"from": accounts[0]}); - await blockchain.increaseTime(20); - }); + const oneYr = 365 * 24 * 60 * 60; + const amount = web3.utils.toWei('200000', 'ether'); + + + const lockPositionForCommityOne = _createLockParamObject(amount,oneYr,comity_1) + const lockPositionForCommityTwo = _createLockParamObject(amount,oneYr,comity_2) + + const _createLockWithoutEarlyWithdrawal = async( + lockParamObject + ) => { + const result = await multiSigWallet.submitTransaction( + stakingService.address, + EMPTY_BYTES, + _encodeCreateLockWithoutEarlyWithdrawal( + lockParamObject + ), + 0, + {"from": accounts[0]} + ) + + const tx = eventsHelper.getIndexedEventArgs(result, SUBMIT_TRANSACTION_EVENT)[0]; + + await multiSigWallet.confirmTransaction(tx, {"from": accounts[0]}); + await multiSigWallet.confirmTransaction(tx, {"from": accounts[1]}); + + await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); + } + await blockchain.mineBlock(await _getTimeStamp() + 100) + await _createLockWithoutEarlyWithdrawal(lockPositionForCommityOne); + await blockchain.mineBlock(await _getTimeStamp() + 100) + await _createLockWithoutEarlyWithdrawal(lockPositionForCommityTwo) + await blockchain.mineBlock(await _getTimeStamp() + 100) + }) it("Check that the lock possitions have been made on behalf of the comity", async() => { @@ -1029,12 +1037,23 @@ describe("DAO Demo", () => { await blockchain.mineBlock(await _getTimeStamp() + 20); const errorMessage = "revert"; - await shouldRevertAndHaveSubstring( + await shouldRevertAndHaveSubstring( stakingService.earlyUnlock(1, {from: comity_1}), errTypes.revert, errorMessage ); }) + + it("Should Revert: early unlock not possible", async() => { + await blockchain.mineBlock(await _getTimeStamp() + 20); + const errorMessage = "revert"; + const lastLockId = await stakingGetterService.getLocksLength(comity_2) + await shouldRevertAndHaveSubstring( + stakingService.earlyUnlock(lastLockId, {from: comity_2}), + errTypes.revert, + errorMessage + ); + }) it("Wait one year and unlock more than was staked, because of staking rewards", async() => { diff --git a/scripts/units/create-fixed-lock-on-behalf-of-user.js b/scripts/units/create-fixed-lock-on-behalf-of-user.js new file mode 100644 index 0000000..b5e892b --- /dev/null +++ b/scripts/units/create-fixed-lock-on-behalf-of-user.js @@ -0,0 +1,81 @@ +//NOTE: This script can be run only once for each deployment as making COUNCIL_STAKES is only possible once +const fs = require('fs'); + +const constants = require('./helpers/constants') +const txnHelper = require('./helpers/submitAndExecuteTransaction') +const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); + +const addressesConfig = require('../../config/config.js') + +const LOCK_PERIOD = 365 * 24 * 60 * 60; +//SET AS NEEDED +// NOT MAX UINT for security as its not good to approve max for Multisig +// this is how much to stake for one council . Right now 10KK +const T_TO_STAKE = web3.utils.toWei('10000000', 'ether'); + +const ACCOUNT_TO_STAKE_FOR = addressesConfig.COUNCIL_1; + +const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); +const addresses = JSON.parse(rawdata); + +const _createLockParamObject = ( + _amount, + _lockPeriod, + _account) => { + return { + amount: _amount, + lockPeriod: _lockPeriod, + account: _account + } +} +const _encodeApproveFunction = (_account, _amount) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name: 'approve', + type: 'function', + inputs: [{ + type: 'address', + name: 'spender' + },{ + type: 'uint256', + name: 'amount' + }] + }, [_account, _amount]); + + return toRet; +} + +const _encodeCreateLocksForCouncils = (_createLockParam) => { + let toRet = web3.eth.abi.encodeFunctionCall({ + name:'createFixedLockOnBehalfOfUserByAdmin', + type:'function', + inputs: [{ + type: 'tuple', + name: 'lockPosition', + components: [ + {"type":"uint256", "name":"amount"}, + {"type":"uint256", "name":"lockPeriod"}, + {"type":"address", "name":"account"} + ] + } + ] + },[_createLockParam]) + return toRet +} + +module.exports = async function(deployer) { + const stakingService = await IStaking.at(addresses.staking); + + await txnHelper.submitAndExecute( + _encodeApproveFunction(stakingService.address,T_TO_STAKE), + addresses.fthmToken, + "ApproveFathomTxn" + ) + + const LockPositionForStaker = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,ACCOUNT_TO_STAKE_FOR) + + await txnHelper.submitAndExecute( + _encodeCreateLocksForCouncils(LockPositionForStaker), + stakingService.address, + "createLocksForCouncilTxn" + ) +} \ No newline at end of file diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index 6bb653a..740d8b3 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -50,11 +50,11 @@ const _encodeApproveFunction = (_account, _amount) => { const _encodeCreateLocksForCouncils = (_createLockParam) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name:'createLocksForCouncils', + name:'createFixedLockOnBehalfOfUserByAdmin', type:'function', inputs: [{ - type: 'tuple[]', - name: 'CreateLockParams', + type: 'tuple', + name: 'lockPosition', components: [ {"type":"uint256", "name":"amount"}, {"type":"uint256", "name":"lockPeriod"}, @@ -75,14 +75,25 @@ module.exports = async function(deployer) { addresses.fthmToken, "ApproveFathomTxn" ) - const LockParamObjectForAllCouncils = [ - _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_1), - _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_2), - _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_3) - ] + + const LockPositionForCouncil_1 = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_1) + const LockPositionForCouncil_2 = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_2) + const LockPositionForCouncil_3 = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_3) + + await txnHelper.submitAndExecute( + _encodeCreateLocksForCouncils(LockPositionForCouncil_1), + stakingService.address, + "createLocksForCouncilTxn" + ) + + await txnHelper.submitAndExecute( + _encodeCreateLocksForCouncils(LockPositionForCouncil_2), + stakingService.address, + "createLocksForCouncilTxn" + ) await txnHelper.submitAndExecute( - _encodeCreateLocksForCouncils(LockParamObjectForAllCouncils), + _encodeCreateLocksForCouncils(LockPositionForCouncil_3), stakingService.address, "createLocksForCouncilTxn" ) From 55f2fc9cb217132e8a53d80f5de26eadc8fa5400 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 13 Apr 2023 14:02:27 +0545 Subject: [PATCH 51/56] minor fix --- contracts/dao/staking/packages/StakingHandler.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index e381b5b..95f3434 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -283,11 +283,10 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A * @dev Admin can create a lock on behalf of a user without early withdrawal */ function createFixedLockOnBehalfOfUserByAdmin(CreateLockParams calldata lockPosition) external override onlyRole(DEFAULT_ADMIN_ROLE) { - address account = lockPosition.account; - if(account == address(0)){ + if(lockPosition.account == address(0)){ revert ZeroAddress(); } - prohibitedEarlyWithdraw[account][locks[account].length + 1] = true; + prohibitedEarlyWithdraw[lockPosition.account][locks[lockPosition.account].length + 1] = true; _createLock(lockPosition.amount, lockPosition.lockPeriod, lockPosition.account); } From 1b800d38e149d93c5fa79cba1fe441b22e86e1b4 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 13 Apr 2023 14:10:34 +0545 Subject: [PATCH 52/56] minor comment fix again --- contracts/dao/staking/StakingStorage.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/dao/staking/StakingStorage.sol b/contracts/dao/staking/StakingStorage.sol index 585a062..c8a2856 100644 --- a/contracts/dao/staking/StakingStorage.sol +++ b/contracts/dao/staking/StakingStorage.sol @@ -17,19 +17,13 @@ contract StakingStorage { uint32 internal constant REWARDS_TO_TREASURY_DENOMINATOR = 10000; uint256 public maxLockPeriod; - ///@notice Checks if the staking is initialized - uint256 public minLockPeriod; - uint256 public maxLockPositions; mapping(address => mapping(uint256 => bool)) internal prohibitedEarlyWithdraw; - uint256 internal touchedAt; - ///@notice The below three are used for autocompounding feature and weighted shares uint256 public totalAmountOfStakedToken; uint256 public totalStreamShares; - ///@notice voteToken -> vote Token uint256 public totalAmountOfVoteToken; uint256 public totalPenaltyBalance; From c787d81125214628c5d40ee80796c3ac97718f1b Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 13 Apr 2023 16:04:42 +0545 Subject: [PATCH 53/56] made create locks for multiple accounts at once for multisig --- .../staking/interfaces/IStakingHandler.sol | 2 +- .../dao/staking/packages/StakingHandler.sol | 13 ++++++---- scripts/tests/dao/full-flow-demo.test.js | 11 ++++---- .../create-fixed-lock-on-behalf-of-user.js | 8 +++--- scripts/units/setup_council_stakes.js | 26 +++++++------------ 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index fea646a..c72641a 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -61,7 +61,7 @@ interface IStakingHandler { function emergencyUnlockAndWithdraw() external; - function createFixedLockOnBehalfOfUserByAdmin(CreateLockParams calldata lockPosition) external; + function createFixedLocksOnBehalfOfUserByAdmin(CreateLockParams[] calldata lockPosition) external; function setMinimumLockPeriod(uint256 _minLockPeriod) external; diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 95f3434..211e491 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -282,12 +282,15 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A /** * @dev Admin can create a lock on behalf of a user without early withdrawal */ - function createFixedLockOnBehalfOfUserByAdmin(CreateLockParams calldata lockPosition) external override onlyRole(DEFAULT_ADMIN_ROLE) { - if(lockPosition.account == address(0)){ - revert ZeroAddress(); + function createFixedLocksOnBehalfOfUserByAdmin(CreateLockParams[] calldata lockPositions) external override onlyRole(DEFAULT_ADMIN_ROLE) { + for (uint256 i; i < lockPositions.length; i++) { + address account = lockPositions[i].account; + if(account == address(0)) { + revert ZeroAddress(); + } + prohibitedEarlyWithdraw[account][locks[account].length + 1] = true; + _createLock(lockPositions[i].amount, lockPositions[i].lockPeriod, account); } - prohibitedEarlyWithdraw[lockPosition.account][locks[lockPosition.account].length + 1] = true; - _createLock(lockPosition.amount, lockPosition.lockPeriod, lockPosition.account); } function createLock(uint256 amount, uint256 lockPeriod) external override pausable(1) { diff --git a/scripts/tests/dao/full-flow-demo.test.js b/scripts/tests/dao/full-flow-demo.test.js index 7c6b3be..5d64197 100644 --- a/scripts/tests/dao/full-flow-demo.test.js +++ b/scripts/tests/dao/full-flow-demo.test.js @@ -150,11 +150,11 @@ const _encodeTransferFunction = (_account, t_to_stake) => { const _encodeCreateLockWithoutEarlyWithdrawal = (_createLockParam) => { // encoded transfer function call for staking on behalf of someone else from treasury. let toRet = web3.eth.abi.encodeFunctionCall({ - name:'createFixedLockOnBehalfOfUserByAdmin', + name:'createFixedLocksOnBehalfOfUserByAdmin', type:'function', inputs: [{ - type: 'tuple', - name: 'lockPosition', + type: 'tuple[]', + name: 'lockPositions', components: [ {"type":"uint256", "name":"amount"}, {"type":"uint256", "name":"lockPeriod"}, @@ -990,6 +990,7 @@ describe("DAO Demo", () => { const lockPositionForCommityOne = _createLockParamObject(amount,oneYr,comity_1) const lockPositionForCommityTwo = _createLockParamObject(amount,oneYr,comity_2) + const allLockPositions = [lockPositionForCommityOne, lockPositionForCommityTwo] const _createLockWithoutEarlyWithdrawal = async( lockParamObject @@ -1012,9 +1013,7 @@ describe("DAO Demo", () => { await multiSigWallet.executeTransaction(tx, {"from": accounts[1]}); } await blockchain.mineBlock(await _getTimeStamp() + 100) - await _createLockWithoutEarlyWithdrawal(lockPositionForCommityOne); - await blockchain.mineBlock(await _getTimeStamp() + 100) - await _createLockWithoutEarlyWithdrawal(lockPositionForCommityTwo) + await _createLockWithoutEarlyWithdrawal(allLockPositions); await blockchain.mineBlock(await _getTimeStamp() + 100) }) diff --git a/scripts/units/create-fixed-lock-on-behalf-of-user.js b/scripts/units/create-fixed-lock-on-behalf-of-user.js index b5e892b..22d44a5 100644 --- a/scripts/units/create-fixed-lock-on-behalf-of-user.js +++ b/scripts/units/create-fixed-lock-on-behalf-of-user.js @@ -46,11 +46,11 @@ const _encodeApproveFunction = (_account, _amount) => { const _encodeCreateLocksForCouncils = (_createLockParam) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name:'createFixedLockOnBehalfOfUserByAdmin', + name:'createFixedLocksOnBehalfOfUserByAdmin', type:'function', inputs: [{ - type: 'tuple', - name: 'lockPosition', + type: 'tuple[]', + name: 'lockPositions', components: [ {"type":"uint256", "name":"amount"}, {"type":"uint256", "name":"lockPeriod"}, @@ -71,7 +71,7 @@ module.exports = async function(deployer) { "ApproveFathomTxn" ) - const LockPositionForStaker = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,ACCOUNT_TO_STAKE_FOR) + const LockPositionForStaker = [_createLockParamObject(T_TO_STAKE,LOCK_PERIOD,ACCOUNT_TO_STAKE_FOR)] await txnHelper.submitAndExecute( _encodeCreateLocksForCouncils(LockPositionForStaker), diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index 740d8b3..1e6d459 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -50,11 +50,11 @@ const _encodeApproveFunction = (_account, _amount) => { const _encodeCreateLocksForCouncils = (_createLockParam) => { let toRet = web3.eth.abi.encodeFunctionCall({ - name:'createFixedLockOnBehalfOfUserByAdmin', + name:'createFixedLocksOnBehalfOfUserByAdmin', type:'function', inputs: [{ - type: 'tuple', - name: 'lockPosition', + type: 'tuple[]', + name: 'lockPositions', components: [ {"type":"uint256", "name":"amount"}, {"type":"uint256", "name":"lockPeriod"}, @@ -80,20 +80,14 @@ module.exports = async function(deployer) { const LockPositionForCouncil_2 = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_2) const LockPositionForCouncil_3 = _createLockParamObject(T_TO_STAKE,LOCK_PERIOD,COUNCIL_3) + const LockParamObjectForAllCouncils = [ + LockPositionForCouncil_1, + LockPositionForCouncil_2, + LockPositionForCouncil_3 + ] + await txnHelper.submitAndExecute( - _encodeCreateLocksForCouncils(LockPositionForCouncil_1), - stakingService.address, - "createLocksForCouncilTxn" - ) - - await txnHelper.submitAndExecute( - _encodeCreateLocksForCouncils(LockPositionForCouncil_2), - stakingService.address, - "createLocksForCouncilTxn" - ) - - await txnHelper.submitAndExecute( - _encodeCreateLocksForCouncils(LockPositionForCouncil_3), + _encodeCreateLocksForCouncils(LockParamObjectForAllCouncils), stakingService.address, "createLocksForCouncilTxn" ) From 70f57251167f71ed0e7a3ecebe1fc2f1b00f85b6 Mon Sep 17 00:00:00 2001 From: ssubik Date: Thu, 13 Apr 2023 18:05:04 +0545 Subject: [PATCH 54/56] npm run pre-release --- contracts/common/Address.sol | 37 ++++- contracts/common/SafeERC20.sol | 42 ++++- contracts/common/SafeERC20Staking.sol | 19 ++- contracts/common/cryptography/ECDSA.sol | 26 ++- contracts/common/cryptography/EIP712.sol | 6 +- contracts/common/math/FullMath.sol | 6 +- contracts/common/proxy/StakingProxy.sol | 6 +- contracts/common/proxy/VaultProxy.sol | 6 +- .../common/proxy/transparent/ProxyAdmin.sol | 6 +- .../TransparentUpgradeableProxy.sol | 6 +- contracts/dao/governance/Governor.sol | 96 +++++++++-- .../dao/governance/MainTokenGovernor.sol | 8 +- .../dao/governance/TimelockController.sol | 13 +- .../extensions/GovernorCountingSimple.sol | 11 +- .../extensions/GovernorSettings.sol | 6 +- .../governance/extensions/GovernorVotes.sol | 6 +- .../dao/governance/extensions/IVotes.sol | 9 +- .../dao/governance/interfaces/IGovernor.sol | 30 +++- .../dao/governance/interfaces/IRelay.sol | 6 +- .../interfaces/ITimelockController.sol | 24 ++- .../staking/helpers/IStakingGetterHelper.sol | 11 +- .../staking/helpers/StakingGettersHelper.sol | 13 +- .../dao/staking/interfaces/IStakingGetter.sol | 18 ++- .../staking/interfaces/IStakingHandler.sol | 7 +- .../staking/packages/RewardsCalculator.sol | 6 +- .../dao/staking/packages/RewardsInternals.sol | 6 +- .../dao/staking/packages/StakingGetters.sol | 20 ++- .../dao/staking/packages/StakingHandler.sol | 8 +- .../dao/staking/packages/StakingInternals.sol | 46 +++++- .../dao/staking/vault/interfaces/IVault.sol | 6 +- .../staking/vault/packages/VaultPackage.sol | 8 +- contracts/dao/test/ERC20Rewards1.sol | 43 ++++- contracts/dao/test/ERC20Rewards2.sol | 43 ++++- contracts/dao/test/VaultProxyMigrate.sol | 6 +- contracts/dao/test/dex/IUniswapV2Router01.sol | 151 ++++++++++-------- contracts/dao/test/dex/IUniswapV2Router02.sol | 36 ++--- .../dao/test/token-factory/ERC20Factory.sol | 12 +- .../interfaces/IERC20Factory.sol | 6 +- .../dao/test/token-timelock/TokenTimelock.sol | 6 +- .../token-timelock/TokenTimelockFactory.sol | 10 +- contracts/dao/tokens/ERC20/ERC20.sol | 36 ++++- contracts/dao/tokens/ERC20/IERC20.sol | 6 +- .../tokens/ERC20/extensions/ERC20Permit.sol | 10 +- .../tokens/ERC20/extensions/ERC20Votes.sol | 21 ++- .../tokens/ERC20/extensions/IERC20Permit.sol | 10 +- contracts/dao/tokens/MainToken.sol | 7 +- contracts/dao/tokens/VMainToken.sol | 12 +- contracts/dao/treasury/MultiSigWallet.sol | 42 +++-- .../treasury/interfaces/IMultiSigWallet.sol | 20 ++- 49 files changed, 777 insertions(+), 218 deletions(-) diff --git a/contracts/common/Address.sol b/contracts/common/Address.sol index d09b2e0..dd23940 100644 --- a/contracts/common/Address.sol +++ b/contracts/common/Address.sol @@ -62,7 +62,11 @@ library Address { * * _Available since v3.1._ */ - function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } @@ -77,7 +81,11 @@ library Address { * * _Available since v3.1._ */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } @@ -87,7 +95,12 @@ library Address { * * _Available since v3.1._ */ - function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); @@ -111,7 +124,11 @@ library Address { * * _Available since v3.4._ */ - function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); @@ -168,7 +185,11 @@ library Address { * * _Available since v3.3._ */ - function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); @@ -189,7 +210,11 @@ library Address { * * _Available since v4.3._ */ - function verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) internal pure returns (bytes memory) { + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { if (success) { return returndata; } else { diff --git a/contracts/common/SafeERC20.sol b/contracts/common/SafeERC20.sol index 23c329c..952c224 100644 --- a/contracts/common/SafeERC20.sol +++ b/contracts/common/SafeERC20.sol @@ -19,11 +19,20 @@ import "./Address.sol"; library SafeERC20 { using Address for address; - function safeTransfer(IERC20 token, address to, uint256 value) internal { + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } @@ -34,7 +43,11 @@ library SafeERC20 { * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ - function safeApprove(IERC20 token, address spender, uint256 value) internal { + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' @@ -42,12 +55,20 @@ library SafeERC20 { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); @@ -56,7 +77,16 @@ library SafeERC20 { } } - function safePermit(IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) internal { + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); diff --git a/contracts/common/SafeERC20Staking.sol b/contracts/common/SafeERC20Staking.sol index 781b4e7..bc893eb 100644 --- a/contracts/common/SafeERC20Staking.sol +++ b/contracts/common/SafeERC20Staking.sol @@ -19,11 +19,20 @@ import "./Address.sol"; library SafeERC20Staking { using Address for address; - function safeTransfer(IERC20 token, address to, uint256 value) internal { + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } @@ -34,7 +43,11 @@ library SafeERC20Staking { * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ - function safeApprove(IERC20 token, address spender, uint256 value) internal { + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' diff --git a/contracts/common/cryptography/ECDSA.sol b/contracts/common/cryptography/ECDSA.sol index 805af22..ec3d38f 100644 --- a/contracts/common/cryptography/ECDSA.sol +++ b/contracts/common/cryptography/ECDSA.sol @@ -101,7 +101,11 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { + function tryRecover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); @@ -112,7 +116,11 @@ library ECDSA { * * _Available since v4.2._ */ - function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { + function recover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; @@ -124,7 +132,12 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { + function tryRecover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most @@ -154,7 +167,12 @@ library ECDSA { * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { + function recover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; diff --git a/contracts/common/cryptography/EIP712.sol b/contracts/common/cryptography/EIP712.sol index a56b536..42352fd 100644 --- a/contracts/common/cryptography/EIP712.sol +++ b/contracts/common/cryptography/EIP712.sol @@ -101,7 +101,11 @@ abstract contract EIP712 { return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); } - function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash, bytes32 versionHash) private view returns (bytes32) { + function _buildDomainSeparator( + bytes32 typeHash, + bytes32 nameHash, + bytes32 versionHash + ) private view returns (bytes32) { return keccak256(abi.encode(typeHash, nameHash, versionHash, getChainID(), address(this))); } } diff --git a/contracts/common/math/FullMath.sol b/contracts/common/math/FullMath.sol index e8d3238..b84ab24 100644 --- a/contracts/common/math/FullMath.sol +++ b/contracts/common/math/FullMath.sol @@ -11,7 +11,11 @@ library FullMath { /// @param denominator The divisor /// @return result The 256-bit result /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = a * b // Compute the product mod 2**256 and mod 2**256 - 1 diff --git a/contracts/common/proxy/StakingProxy.sol b/contracts/common/proxy/StakingProxy.sol index 43f42ad..33c2298 100644 --- a/contracts/common/proxy/StakingProxy.sol +++ b/contracts/common/proxy/StakingProxy.sol @@ -5,5 +5,9 @@ pragma solidity 0.8.16; import "./transparent/TransparentUpgradeableProxy.sol"; contract StakingProxy is TransparentUpgradeableProxy { - constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} } diff --git a/contracts/common/proxy/VaultProxy.sol b/contracts/common/proxy/VaultProxy.sol index d638616..d311802 100644 --- a/contracts/common/proxy/VaultProxy.sol +++ b/contracts/common/proxy/VaultProxy.sol @@ -5,5 +5,9 @@ pragma solidity 0.8.16; import "./transparent/TransparentUpgradeableProxy.sol"; contract VaultProxy is TransparentUpgradeableProxy { - constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} } diff --git a/contracts/common/proxy/transparent/ProxyAdmin.sol b/contracts/common/proxy/transparent/ProxyAdmin.sol index 3f9e7e6..983e2c9 100644 --- a/contracts/common/proxy/transparent/ProxyAdmin.sol +++ b/contracts/common/proxy/transparent/ProxyAdmin.sol @@ -71,7 +71,11 @@ contract ProxyAdmin is Ownable { * * - This contract must be the admin of `proxy`. */ - function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner { + function upgradeAndCall( + TransparentUpgradeableProxy proxy, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { proxy.upgradeToAndCall{ value: msg.value }(implementation, data); } } diff --git a/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol index 9b04d91..5a9dc76 100644 --- a/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/common/proxy/transparent/TransparentUpgradeableProxy.sol @@ -31,7 +31,11 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ - constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable ERC1967Proxy(_logic, _data) { _changeAdmin(admin_); } diff --git a/contracts/dao/governance/Governor.sol b/contracts/dao/governance/Governor.sol index 1adf31a..aea53dd 100644 --- a/contracts/dao/governance/Governor.sol +++ b/contracts/dao/governance/Governor.sol @@ -227,7 +227,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return _castVote(proposalId, voter, support, ""); } - function castVoteWithReason(uint256 proposalId, uint8 support, string memory reason) public virtual override returns (uint256) { + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string memory reason + ) public virtual override returns (uint256) { if (live != 1) { revert NotLive(); } @@ -248,7 +252,13 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return _castVote(proposalId, voter, support, reason, params); } - function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) public virtual override returns (uint256) { + function castVoteBySig( + uint256 proposalId, + uint8 support, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override returns (uint256) { if (live != 1) { revert NotLive(); } @@ -367,7 +377,16 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { isBlocklisted[account] = blocklistStatus; } - function getProposals(uint256 _numIndexes) public view override returns (string[] memory, string[] memory, string[] memory) { + function getProposals(uint256 _numIndexes) + public + view + override + returns ( + string[] memory, + string[] memory, + string[] memory + ) + { uint256 len = _proposalIds.length; if (len == 0) { @@ -394,7 +413,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return _getVotes(account, blockNumber, _defaultParams()); } - function getVotesWithParams(address account, uint256 blockNumber, bytes memory params) public view virtual override returns (uint256) { + function getVotesWithParams( + address account, + uint256 blockNumber, + bytes memory params + ) public view virtual override returns (uint256) { return _getVotes(account, blockNumber, params); } @@ -502,10 +525,16 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { emit EmergencyStop(); } - function _countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes memory params) internal virtual; + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory params + ) internal virtual; function _execute( - uint256 /*proposalId*/, + uint256, /*proposalId*/ address[] memory targets, uint256[] memory values, bytes[] memory calldatas, @@ -518,9 +547,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } function _beforeExecute( - uint256 /* proposalId */, + uint256, /* proposalId */ address[] memory targets, - uint256[] memory /* values */, + uint256[] memory, /* values */ bytes[] memory calldatas, bytes32 /*descriptionHash*/ ) internal virtual { @@ -534,10 +563,10 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { } function _afterExecute( - uint256 /* proposalId */, - address[] memory /* targets */, - uint256[] memory /* values */, - bytes[] memory /* calldatas */, + uint256, /* proposalId */ + address[] memory, /* targets */ + uint256[] memory, /* values */ + bytes[] memory, /* calldatas */ bytes32 /*descriptionHash*/ ) internal virtual { if (_executor() != address(this)) { @@ -566,7 +595,12 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return proposalId; } - function _castVote(uint256 proposalId, address account, uint8 support, string memory reason) internal virtual returns (uint256) { + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason + ) internal virtual returns (uint256) { return _castVote(proposalId, account, support, reason, _defaultParams()); } @@ -601,7 +635,15 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { nextAcceptableProposalTimestamp[account] = block.timestamp + proposalTimeDelay; } - function _getProposals1(uint256 _numIndexes) internal view returns (string[] memory, string[] memory, string[] memory) { + function _getProposals1(uint256 _numIndexes) + internal + view + returns ( + string[] memory, + string[] memory, + string[] memory + ) + { string[] memory statuses = new string[](_numIndexes); string[] memory descriptionsArray = new string[](_numIndexes); string[] memory proposalIds = new string[](_numIndexes); @@ -634,7 +676,15 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return (proposalIds, descriptionsArray, statuses); } - function _getProposalsAll(uint256 len) internal view returns (string[] memory, string[] memory, string[] memory) { + function _getProposalsAll(uint256 len) + internal + view + returns ( + string[] memory, + string[] memory, + string[] memory + ) + { string[] memory statuses = new string[](len); string[] memory descriptionsArray = new string[](len); string[] memory proposalIds = new string[](len); @@ -655,7 +705,15 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { return (proposalIds, descriptionsArray, statuses); } - function _getProposals(uint256 _numIndexes, uint256 len) internal view returns (string[] memory, string[] memory, string[] memory) { + function _getProposals(uint256 _numIndexes, uint256 len) + internal + view + returns ( + string[] memory, + string[] memory, + string[] memory + ) + { string[] memory statuses = new string[](_numIndexes); string[] memory descriptionsArray = new string[](_numIndexes); string[] memory proposalIds = new string[](_numIndexes); @@ -698,7 +756,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); - function _getVotes(address account, uint256 blockNumber, bytes memory params) internal view virtual returns (uint256); + function _getVotes( + address account, + uint256 blockNumber, + bytes memory params + ) internal view virtual returns (uint256); function _defaultParams() internal view virtual returns (bytes memory) { return ""; diff --git a/contracts/dao/governance/MainTokenGovernor.sol b/contracts/dao/governance/MainTokenGovernor.sol index 28d172c..26b53fe 100644 --- a/contracts/dao/governance/MainTokenGovernor.sol +++ b/contracts/dao/governance/MainTokenGovernor.sol @@ -70,7 +70,11 @@ contract MainTokenGovernor is * in a governance proposal to recover Ether that was sent to the governor contract by mistake. * Note that if the executor is simply the governor itself, use of `relay` is redundant. */ - function relayNativeToken(address target, uint256 value, bytes calldata data) external payable virtual override onlyGovernance { + function relayNativeToken( + address target, + uint256 value, + bytes calldata data + ) external payable virtual override onlyGovernance { if (isSupportedToken[target]) { revert TokenSupported(); } @@ -105,7 +109,7 @@ contract MainTokenGovernor is */ function emergencyStop() public override onlyMultiSig { _emergencyStop(); - for (uint i = 0; i < listOfSupportedTokens.length; i++) { + for (uint256 i = 0; i < listOfSupportedTokens.length; i++) { address _token = listOfSupportedTokens[i]; uint256 balanceInContract = IERC20(_token).balanceOf(address(this)); if (balanceInContract > 0) { diff --git a/contracts/dao/governance/TimelockController.sol b/contracts/dao/governance/TimelockController.sol index 8028934..7721c58 100644 --- a/contracts/dao/governance/TimelockController.sol +++ b/contracts/dao/governance/TimelockController.sol @@ -64,7 +64,12 @@ contract TimelockController is AccessControl, Initializable, ITimelockController // solhint-disable-next-line comprehensive-interface receive() external payable {} - function initialize(uint256 minDelay, address admin, address[] memory proposers, address[] memory executors) public override initializer { + function initialize( + uint256 minDelay, + address admin, + address[] memory proposers, + address[] memory executors + ) public override initializer { if (minDelay == 0) { revert ZeroValue(); } @@ -266,7 +271,11 @@ contract TimelockController is AccessControl, Initializable, ITimelockController return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); } - function _execute(address target, uint256 value, bytes memory data) internal virtual { + function _execute( + address target, + uint256 value, + bytes memory data + ) internal virtual { (bool success, ) = target.call{ value: value }(data); emit ExecuteTransaction(msg.sender, success, data); } diff --git a/contracts/dao/governance/extensions/GovernorCountingSimple.sol b/contracts/dao/governance/extensions/GovernorCountingSimple.sol index 441e2e9..defe874 100644 --- a/contracts/dao/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/dao/governance/extensions/GovernorCountingSimple.sol @@ -29,7 +29,16 @@ abstract contract GovernorCountingSimple is Governor { return _proposalVotes[proposalId].hasVoted[account]; } - function proposalVotes(uint256 proposalId) external view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { + function proposalVotes(uint256 proposalId) + external + view + virtual + returns ( + uint256 againstVotes, + uint256 forVotes, + uint256 abstainVotes + ) + { ProposalVote storage proposalvote = _proposalVotes[proposalId]; return (proposalvote.againstVotes, proposalvote.forVotes, proposalvote.abstainVotes); } diff --git a/contracts/dao/governance/extensions/GovernorSettings.sol b/contracts/dao/governance/extensions/GovernorSettings.sol index 7a09763..2abd02b 100644 --- a/contracts/dao/governance/extensions/GovernorSettings.sol +++ b/contracts/dao/governance/extensions/GovernorSettings.sol @@ -18,7 +18,11 @@ abstract contract GovernorSettings is Governor { error ZeroVotePeriod(); error ZeroThreshold(); - constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold) { + constructor( + uint256 initialVotingDelay, + uint256 initialVotingPeriod, + uint256 initialProposalThreshold + ) { _setVotingDelay(initialVotingDelay); _setVotingPeriod(initialVotingPeriod); _setProposalThreshold(initialProposalThreshold); diff --git a/contracts/dao/governance/extensions/GovernorVotes.sol b/contracts/dao/governance/extensions/GovernorVotes.sol index 83cc31d..21d21f2 100644 --- a/contracts/dao/governance/extensions/GovernorVotes.sol +++ b/contracts/dao/governance/extensions/GovernorVotes.sol @@ -17,7 +17,11 @@ abstract contract GovernorVotes is Governor { token = tokenAddress; } - function _getVotes(address account, uint256 blockNumber, bytes memory /*params*/) internal view virtual override returns (uint256) { + function _getVotes( + address account, + uint256 blockNumber, + bytes memory /*params*/ + ) internal view virtual override returns (uint256) { return token.getPastVotes(account, blockNumber); } } diff --git a/contracts/dao/governance/extensions/IVotes.sol b/contracts/dao/governance/extensions/IVotes.sol index 9e4dd6f..9e89bd9 100644 --- a/contracts/dao/governance/extensions/IVotes.sol +++ b/contracts/dao/governance/extensions/IVotes.sol @@ -10,7 +10,14 @@ interface IVotes { function delegate(address delegatee) external; - function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external; function getVotes(address account) external view returns (uint256); diff --git a/contracts/dao/governance/interfaces/IGovernor.sol b/contracts/dao/governance/interfaces/IGovernor.sol index 2a65ded..fe75035 100644 --- a/contracts/dao/governance/interfaces/IGovernor.sol +++ b/contracts/dao/governance/interfaces/IGovernor.sol @@ -61,7 +61,11 @@ abstract contract IGovernor is IERC165 { function castVote(uint256 proposalId, uint8 support) external virtual returns (uint256 balance); - function castVoteWithReason(uint256 proposalId, uint8 support, string calldata reason) external virtual returns (uint256 balance); + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string calldata reason + ) external virtual returns (uint256 balance); function castVoteWithReasonAndParams( uint256 proposalId, @@ -70,7 +74,13 @@ abstract contract IGovernor is IERC165 { bytes calldata params ) external virtual returns (uint256 balance); - function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external virtual returns (uint256 balance); + function castVoteBySig( + uint256 proposalId, + uint8 support, + uint8 v, + bytes32 r, + bytes32 s + ) external virtual returns (uint256 balance); function castVoteWithReasonAndParamsBySig( uint256 proposalId, @@ -82,7 +92,15 @@ abstract contract IGovernor is IERC165 { bytes32 s ) external virtual returns (uint256 balance); - function getProposals(uint256 _numIndexes) external view virtual returns (string[] memory, string[] memory, string[] memory); + function getProposals(uint256 _numIndexes) + external + view + virtual + returns ( + string[] memory, + string[] memory, + string[] memory + ); function getDescription(uint256 _proposalId) external view virtual returns (string memory); @@ -100,7 +118,11 @@ abstract contract IGovernor is IERC165 { function getVotes(address account, uint256 blockNumber) external view virtual returns (uint256); - function getVotesWithParams(address account, uint256 blockNumber, bytes calldata params) external view virtual returns (uint256); + function getVotesWithParams( + address account, + uint256 blockNumber, + bytes calldata params + ) external view virtual returns (uint256); function hasVoted(uint256 proposalId, address account) external view virtual returns (bool); diff --git a/contracts/dao/governance/interfaces/IRelay.sol b/contracts/dao/governance/interfaces/IRelay.sol index 919bd69..3982d79 100644 --- a/contracts/dao/governance/interfaces/IRelay.sol +++ b/contracts/dao/governance/interfaces/IRelay.sol @@ -17,5 +17,9 @@ interface IRelay { * in a governance proposal to recover Ether that was sent to the governor contract by mistake. * Note that if the executor is simply the governor itself, use of `relay` is redundant. */ - function relayNativeToken(address target, uint256 value, bytes calldata data) external payable; + function relayNativeToken( + address target, + uint256 value, + bytes calldata data + ) external payable; } diff --git a/contracts/dao/governance/interfaces/ITimelockController.sol b/contracts/dao/governance/interfaces/ITimelockController.sol index 95132a2..b12d0ee 100644 --- a/contracts/dao/governance/interfaces/ITimelockController.sol +++ b/contracts/dao/governance/interfaces/ITimelockController.sol @@ -4,9 +4,21 @@ pragma solidity 0.8.16; interface ITimelockController { - function initialize(uint256 minDelay, address admin, address[] calldata proposers, address[] calldata executors) external; + function initialize( + uint256 minDelay, + address admin, + address[] calldata proposers, + address[] calldata executors + ) external; - function schedule(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay) external; + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; function scheduleBatch( address[] calldata targets, @@ -19,7 +31,13 @@ interface ITimelockController { function cancel(bytes32 id) external; - function execute(address target, uint256 value, bytes calldata payload, bytes32 predecessor, bytes32 salt) external payable; + function execute( + address target, + uint256 value, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt + ) external payable; function executeBatch( address[] calldata targets, diff --git a/contracts/dao/staking/helpers/IStakingGetterHelper.sol b/contracts/dao/staking/helpers/IStakingGetterHelper.sol index 4d752ff..38421b5 100644 --- a/contracts/dao/staking/helpers/IStakingGetterHelper.sol +++ b/contracts/dao/staking/helpers/IStakingGetterHelper.sol @@ -11,7 +11,16 @@ import "../../../common/security/IAdminPausable.sol"; interface IStakingGetterHelper { function getLockInfo(address account, uint256 lockId) external view returns (LockedBalance memory); - function getLock(address account, uint256 lockId) external view returns (uint128, uint128, uint64, address, uint256); + function getLock(address account, uint256 lockId) + external + view + returns ( + uint128, + uint128, + uint64, + address, + uint256 + ); function getUserTotalDeposit(address account) external view returns (uint256); diff --git a/contracts/dao/staking/helpers/StakingGettersHelper.sol b/contracts/dao/staking/helpers/StakingGettersHelper.sol index 52a4403..fc1b301 100644 --- a/contracts/dao/staking/helpers/StakingGettersHelper.sol +++ b/contracts/dao/staking/helpers/StakingGettersHelper.sol @@ -30,7 +30,18 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl { return _getWeight(); } - function getLock(address account, uint256 lockId) external view override returns (uint128, uint128, uint64, address, uint256) { + function getLock(address account, uint256 lockId) + external + view + override + returns ( + uint128, + uint128, + uint64, + address, + uint256 + ) + { LockedBalance memory lock = getLockInfo(account, lockId); return (lock.amountOfToken, lock.positionStreamShares, lock.end, lock.owner, lock.amountOfVoteToken); } diff --git a/contracts/dao/staking/interfaces/IStakingGetter.sol b/contracts/dao/staking/interfaces/IStakingGetter.sol index 4dba3c5..2c3ab66 100644 --- a/contracts/dao/staking/interfaces/IStakingGetter.sol +++ b/contracts/dao/staking/interfaces/IStakingGetter.sol @@ -10,11 +10,21 @@ interface IStakingGetter { function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256); - function getStreamClaimableAmountPerLock(uint256 streamId, address account, uint256 lockId) external view returns (uint256); + function getStreamClaimableAmountPerLock( + uint256 streamId, + address account, + uint256 lockId + ) external view returns (uint256); function getStreamSchedule(uint256 streamId) external view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards); - function getStream( - uint256 streamId - ) external view returns (uint256 rewardDepositAmount, uint256 rewardClaimedAmount, uint256 rps, StreamStatus status); + function getStream(uint256 streamId) + external + view + returns ( + uint256 rewardDepositAmount, + uint256 rewardClaimedAmount, + uint256 rps, + StreamStatus status + ); } diff --git a/contracts/dao/staking/interfaces/IStakingHandler.sol b/contracts/dao/staking/interfaces/IStakingHandler.sol index c72641a..fa53047 100644 --- a/contracts/dao/staking/interfaces/IStakingHandler.sol +++ b/contracts/dao/staking/interfaces/IStakingHandler.sol @@ -20,7 +20,12 @@ interface IStakingHandler { uint256 _minLockPeriod ) external; - function initializeMainStream(address _owner, uint256[] calldata scheduleTimes, uint256[] calldata scheduleRewards, uint256 tau) external; + function initializeMainStream( + address _owner, + uint256[] calldata scheduleTimes, + uint256[] calldata scheduleRewards, + uint256 tau + ) external; function proposeStream( address streamOwner, diff --git a/contracts/dao/staking/packages/RewardsCalculator.sol b/contracts/dao/staking/packages/RewardsCalculator.sol index 41b4705..e76147c 100644 --- a/contracts/dao/staking/packages/RewardsCalculator.sol +++ b/contracts/dao/staking/packages/RewardsCalculator.sol @@ -111,7 +111,11 @@ contract RewardsCalculator is IRewardsHandler { return _getRewardsSchedule(schedule, start, end); } - function _getRewardsSchedule(Schedule memory schedule, uint256 start, uint256 end) internal pure returns (uint256) { + function _getRewardsSchedule( + Schedule memory schedule, + uint256 start, + uint256 end + ) internal pure returns (uint256) { uint256 startIndex; uint256 endIndex; (startIndex, endIndex) = _getStartEndScheduleIndex(schedule, start, end); diff --git a/contracts/dao/staking/packages/RewardsInternals.sol b/contracts/dao/staking/packages/RewardsInternals.sol index 36bfd29..cd79505 100644 --- a/contracts/dao/staking/packages/RewardsInternals.sol +++ b/contracts/dao/staking/packages/RewardsInternals.sol @@ -23,7 +23,11 @@ contract RewardsInternals is StakingStorage, IStakingEvents { } } - function _moveRewardsToPending(address account, uint256 streamId, uint256 lockId) internal { + function _moveRewardsToPending( + address account, + uint256 streamId, + uint256 lockId + ) internal { if (streams[streamId].status != StreamStatus.ACTIVE) { revert InactiveStreamError(); } diff --git a/contracts/dao/staking/packages/StakingGetters.sol b/contracts/dao/staking/packages/StakingGetters.sol index 1293549..0ed8598 100644 --- a/contracts/dao/staking/packages/StakingGetters.sol +++ b/contracts/dao/staking/packages/StakingGetters.sol @@ -16,7 +16,11 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { return users[account].pendings[streamId]; } - function getStreamClaimableAmountPerLock(uint256 streamId, address account, uint256 lockId) external view override returns (uint256) { + function getStreamClaimableAmountPerLock( + uint256 streamId, + address account, + uint256 lockId + ) external view override returns (uint256) { if (streams[streamId].status != StreamStatus.ACTIVE) { revert StreamInactiveError(); } @@ -39,9 +43,17 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals { return (streams[streamId].schedule.time, streams[streamId].schedule.reward); } - function getStream( - uint256 streamId - ) external view override returns (uint256 rewardDepositAmount, uint256 rewardClaimedAmount, uint256 rps, StreamStatus status) { + function getStream(uint256 streamId) + external + view + override + returns ( + uint256 rewardDepositAmount, + uint256 rewardClaimedAmount, + uint256 rps, + StreamStatus status + ) + { Stream storage stream = streams[streamId]; return (stream.rewardDepositAmount, stream.rewardClaimedAmount, stream.rps, stream.status); } diff --git a/contracts/dao/staking/packages/StakingHandler.sol b/contracts/dao/staking/packages/StakingHandler.sol index 211e491..54001eb 100644 --- a/contracts/dao/staking/packages/StakingHandler.sol +++ b/contracts/dao/staking/packages/StakingHandler.sol @@ -285,7 +285,7 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A function createFixedLocksOnBehalfOfUserByAdmin(CreateLockParams[] calldata lockPositions) external override onlyRole(DEFAULT_ADMIN_ROLE) { for (uint256 i; i < lockPositions.length; i++) { address account = lockPositions[i].account; - if(account == address(0)) { + if (account == address(0)) { revert ZeroAddress(); } prohibitedEarlyWithdraw[account][locks[account].length + 1] = true; @@ -462,7 +462,11 @@ contract StakingHandlers is StakingStorage, IStakingHandler, StakingInternals, A treasury = newTreasury; } - function _createLock(uint256 amount, uint256 lockPeriod, address account) internal { + function _createLock( + uint256 amount, + uint256 lockPeriod, + address account + ) internal { if (lockPeriod < minLockPeriod) { revert MinLockPeriodNotMet(); } diff --git a/contracts/dao/staking/packages/StakingInternals.sol b/contracts/dao/staking/packages/StakingInternals.sol index 5c4a043..417a8aa 100644 --- a/contracts/dao/staking/packages/StakingInternals.sol +++ b/contracts/dao/staking/packages/StakingInternals.sol @@ -43,7 +43,11 @@ contract StakingInternals is RewardsInternals { voteLockCoef = _voteLockCoef; } - function _lock(address account, uint256 amount, uint256 lockPeriod) internal { + function _lock( + address account, + uint256 amount, + uint256 lockPeriod + ) internal { uint256 nVoteToken; User storage userAccount = users[account]; if (lockPeriod > 0) { @@ -77,7 +81,12 @@ contract StakingInternals is RewardsInternals { * @notice If the lock position is completely unlocked then the last lock is swapped with current locked * and last lock is popped off. */ - function _unlock(uint256 stakeValue, uint256 amount, uint256 lockId, address account) internal { + function _unlock( + uint256 stakeValue, + uint256 amount, + uint256 lockId, + address account + ) internal { User storage userAccount = users[account]; LockedBalance storage updateLock = locks[account][lockId - 1]; @@ -115,7 +124,12 @@ contract StakingInternals is RewardsInternals { * @notice the amount of stream shares you receive decreases from 100% to 25% * @notice the amount of stream shares you receive depends upon when in the timeline you have staked */ - function _stake(address account, uint256 amount, uint256 nVoteToken, uint256 lockId) internal { + function _stake( + address account, + uint256 amount, + uint256 nVoteToken, + uint256 lockId + ) internal { User storage userAccount = users[account]; LockedBalance storage lock = locks[account][lockId - 1]; @@ -134,7 +148,12 @@ contract StakingInternals is RewardsInternals { emit Staked(account, amount, weightedAmountOfSharesPerStream, nVoteToken, lockId, lock.end); } - function _unstake(uint256 amount, uint256 stakeValue, uint256 lockId, address account) internal { + function _unstake( + uint256 amount, + uint256 stakeValue, + uint256 lockId, + address account + ) internal { User storage userAccount = users[account]; LockedBalance storage updateLock = locks[account][lockId - 1]; totalAmountOfStakedToken -= stakeValue; @@ -158,7 +177,12 @@ contract StakingInternals is RewardsInternals { } } - function _restakeThePosition(uint256 amountToRestake, uint256 lockId, LockedBalance storage updateLock, User storage userAccount) internal { + function _restakeThePosition( + uint256 amountToRestake, + uint256 lockId, + LockedBalance storage updateLock, + User storage userAccount + ) internal { totalAmountOfStakedToken += amountToRestake; updateLock.amountOfToken += BoringMath.to128(amountToRestake); ///@notice if you unstake, early or partial or complete, @@ -198,7 +222,11 @@ contract StakingInternals is RewardsInternals { totalPenaltyBalance += penalty; } - function _removeLockPosition(User storage userAccount, address account, uint256 lockId) internal { + function _removeLockPosition( + User storage userAccount, + address account, + uint256 lockId + ) internal { uint256 streamsLength = streams.length; uint256 lastLockId = locks[account].length; if (lastLockId != lockId && lastLockId > 1) { @@ -229,7 +257,11 @@ contract StakingInternals is RewardsInternals { IVault(vault).payRewards(accountTo, mainToken, pendingPenalty); } - function _weightedShares(uint256 amountOfTokenShares, uint256 nVoteToken, uint256 timestamp) internal view returns (uint256) { + function _weightedShares( + uint256 amountOfTokenShares, + uint256 nVoteToken, + uint256 timestamp + ) internal view returns (uint256) { ///@notice Shares accomodate vote the amount of tokenShares and vote Tokens to be released ///@notice This formula makes it so that both the time locked for Main token and the amount of token locked /// is used to calculate rewards diff --git a/contracts/dao/staking/vault/interfaces/IVault.sol b/contracts/dao/staking/vault/interfaces/IVault.sol index bbeba8c..3f749c7 100644 --- a/contracts/dao/staking/vault/interfaces/IVault.sol +++ b/contracts/dao/staking/vault/interfaces/IVault.sol @@ -12,7 +12,11 @@ interface IVault { function removeSupportedToken(address _token) external; - function payRewards(address _user, address _token, uint256 _deposit) external; + function payRewards( + address _user, + address _token, + uint256 _deposit + ) external; function migrate(address vaultPackageMigrateTo) external; diff --git a/contracts/dao/staking/vault/packages/VaultPackage.sol b/contracts/dao/staking/vault/packages/VaultPackage.sol index 8dd6c20..6a71843 100644 --- a/contracts/dao/staking/vault/packages/VaultPackage.sol +++ b/contracts/dao/staking/vault/packages/VaultPackage.sol @@ -46,7 +46,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { _grantRole(REWARDS_OPERATOR_ROLE, _rewardsOperator); } - function payRewards(address _user, address _token, uint256 _amount) external override pausable(1) { + function payRewards( + address _user, + address _token, + uint256 _amount + ) external override pausable(1) { if (!hasRole(REWARDS_OPERATOR_ROLE, msg.sender)) revert NoRewardsOperatorRole(); if (!isSupportedToken[_token]) revert UnsupportedToken(); if (_amount == 0) revert AmountZero(); @@ -94,7 +98,7 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable { } function withdrawExtraSupportedTokens(address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) { - for (uint i = 0; i < listOfSupportedTokens.length; i++) { + for (uint256 i = 0; i < listOfSupportedTokens.length; i++) { uint256 balanceToWithdraw; address _token = listOfSupportedTokens[i]; uint256 balanceInContract = IERC20(_token).balanceOf(address(this)); diff --git a/contracts/dao/test/ERC20Rewards1.sol b/contracts/dao/test/ERC20Rewards1.sol index 0d51bad..af025a8 100644 --- a/contracts/dao/test/ERC20Rewards1.sol +++ b/contracts/dao/test/ERC20Rewards1.sol @@ -52,7 +52,12 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * All two of these values are immutable: they can only be set once during * construction. */ - constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address _multiSigTreasury) { + constructor( + string memory name_, + string memory symbol_, + uint256 totalSupply_, + address _multiSigTreasury + ) { _name = name_; _symbol = symbol_; _totalSupply = totalSupply_; @@ -107,7 +112,11 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -226,7 +235,11 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ - function _transfer(address from, address to, uint256 amount) internal virtual { + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); @@ -306,7 +319,11 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve(address owner, address spender, uint256 amount) internal virtual { + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); @@ -322,7 +339,11 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * Might emit an {Approval} event. */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); @@ -346,7 +367,11 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes @@ -362,5 +387,9 @@ contract ERC20Rewards1 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} } diff --git a/contracts/dao/test/ERC20Rewards2.sol b/contracts/dao/test/ERC20Rewards2.sol index c3845db..8562b48 100644 --- a/contracts/dao/test/ERC20Rewards2.sol +++ b/contracts/dao/test/ERC20Rewards2.sol @@ -53,7 +53,12 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * construction. */ - constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address issuer) { + constructor( + string memory name_, + string memory symbol_, + uint256 totalSupply_, + address issuer + ) { _name = name_; _symbol = symbol_; _totalSupply = totalSupply_; @@ -108,7 +113,11 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -227,7 +236,11 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ - function _transfer(address from, address to, uint256 amount) internal virtual { + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); @@ -307,7 +320,11 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve(address owner, address spender, uint256 amount) internal virtual { + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); @@ -323,7 +340,11 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * Might emit an {Approval} event. */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); @@ -347,7 +368,11 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes @@ -363,5 +388,9 @@ contract ERC20Rewards2 is Context, IERC20, IERC20Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} } diff --git a/contracts/dao/test/VaultProxyMigrate.sol b/contracts/dao/test/VaultProxyMigrate.sol index 54bc6c8..42fb8f6 100644 --- a/contracts/dao/test/VaultProxyMigrate.sol +++ b/contracts/dao/test/VaultProxyMigrate.sol @@ -5,5 +5,9 @@ pragma solidity 0.8.16; import "../../common/proxy/transparent/TransparentUpgradeableProxy.sol"; contract VaultProxyMigrate is TransparentUpgradeableProxy { - constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} } diff --git a/contracts/dao/test/dex/IUniswapV2Router01.sol b/contracts/dao/test/dex/IUniswapV2Router01.sol index bd22d4a..62c4546 100644 --- a/contracts/dao/test/dex/IUniswapV2Router01.sol +++ b/contracts/dao/test/dex/IUniswapV2Router01.sol @@ -12,122 +12,147 @@ interface IUniswapV2Router01 { function addLiquidity( address tokenA, address tokenB, - uint amountADesired, - uint amountBDesired, - uint amountAMin, - uint amountBMin, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, address to, - uint deadline - ) external returns (uint amountA, uint amountB, uint liquidity); + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); function addLiquidityETH( address token, - uint amountTokenDesired, - uint amountTokenMin, - uint amountETHMin, + uint256 amountTokenDesired, + uint256 amountTokenMin, + uint256 amountETHMin, address to, - uint deadline - ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + uint256 deadline + ) + external + payable + returns ( + uint256 amountToken, + uint256 amountETH, + uint256 liquidity + ); function removeLiquidity( address tokenA, address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, address to, - uint deadline - ) external returns (uint amountA, uint amountB); + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); function removeLiquidityETH( address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, address to, - uint deadline - ) external returns (uint amountToken, uint amountETH); + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountETH); function removeLiquidityWithPermit( address tokenA, address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, address to, - uint deadline, + uint256 deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external returns (uint amountA, uint amountB); + ) external returns (uint256 amountA, uint256 amountB); function removeLiquidityETHWithPermit( address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, address to, - uint deadline, + uint256 deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external returns (uint amountToken, uint amountETH); + ) external returns (uint256 amountToken, uint256 amountETH); function swapExactTokensForTokens( - uint amountIn, - uint amountOutMin, + uint256 amountIn, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline - ) external returns (uint[] memory amounts); + uint256 deadline + ) external returns (uint256[] memory amounts); function swapTokensForExactTokens( - uint amountOut, - uint amountInMax, + uint256 amountOut, + uint256 amountInMax, address[] calldata path, address to, - uint deadline - ) external returns (uint[] memory amounts); + uint256 deadline + ) external returns (uint256[] memory amounts); function swapExactETHForTokens( - uint amountOutMin, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline - ) external payable returns (uint[] memory amounts); + uint256 deadline + ) external payable returns (uint256[] memory amounts); function swapTokensForExactETH( - uint amountOut, - uint amountInMax, + uint256 amountOut, + uint256 amountInMax, address[] calldata path, address to, - uint deadline - ) external returns (uint[] memory amounts); + uint256 deadline + ) external returns (uint256[] memory amounts); function swapExactTokensForETH( - uint amountIn, - uint amountOutMin, + uint256 amountIn, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline - ) external returns (uint[] memory amounts); + uint256 deadline + ) external returns (uint256[] memory amounts); function swapETHForExactTokens( - uint amountOut, + uint256 amountOut, address[] calldata path, address to, - uint deadline - ) external payable returns (uint[] memory amounts); - - function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); - - function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); - - function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); - - function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); - - function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function quote( + uint256 amountA, + uint256 reserveA, + uint256 reserveB + ) external pure returns (uint256 amountB); + + function getAmountOut( + uint256 amountIn, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountOut); + + function getAmountIn( + uint256 amountOut, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountIn); + + function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts); } diff --git a/contracts/dao/test/dex/IUniswapV2Router02.sol b/contracts/dao/test/dex/IUniswapV2Router02.sol index d796292..a74c224 100644 --- a/contracts/dao/test/dex/IUniswapV2Router02.sol +++ b/contracts/dao/test/dex/IUniswapV2Router02.sol @@ -9,46 +9,46 @@ import "./IUniswapV2Router01.sol"; interface IUniswapV2Router02 is IUniswapV2Router01 { function removeLiquidityETHSupportingFeeOnTransferTokens( address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, address to, - uint deadline - ) external returns (uint amountETH); + uint256 deadline + ) external returns (uint256 amountETH); function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, address to, - uint deadline, + uint256 deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external returns (uint amountETH); + ) external returns (uint256 amountETH); function swapExactTokensForTokensSupportingFeeOnTransferTokens( - uint amountIn, - uint amountOutMin, + uint256 amountIn, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline + uint256 deadline ) external; function swapExactETHForTokensSupportingFeeOnTransferTokens( - uint amountOutMin, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline + uint256 deadline ) external payable; function swapExactTokensForETHSupportingFeeOnTransferTokens( - uint amountIn, - uint amountOutMin, + uint256 amountIn, + uint256 amountOutMin, address[] calldata path, address to, - uint deadline + uint256 deadline ) external; } diff --git a/contracts/dao/test/token-factory/ERC20Factory.sol b/contracts/dao/test/token-factory/ERC20Factory.sol index 462d6a8..9ef6c05 100644 --- a/contracts/dao/test/token-factory/ERC20Factory.sol +++ b/contracts/dao/test/token-factory/ERC20Factory.sol @@ -6,7 +6,11 @@ import "../../tokens/ERC20/ERC20.sol"; import "../../../common/access/AccessControl.sol"; contract Token is ERC20 { - constructor(string memory _name, string memory _ticker, uint256 _supply) ERC20(_name, _ticker) { + constructor( + string memory _name, + string memory _ticker, + uint256 _supply + ) ERC20(_name, _ticker) { _mint(msg.sender, _supply); } } @@ -22,7 +26,11 @@ contract ERC20Factory is IERC20Factory, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function deployToken(string calldata _name, string calldata _ticker, uint256 _supply) public override onlyRole(DEPLOYER_ROLE) returns (address) { + function deployToken( + string calldata _name, + string calldata _ticker, + uint256 _supply + ) public override onlyRole(DEPLOYER_ROLE) returns (address) { Token token = new Token(_name, _ticker, _supply); tokens.push(address(token)); diff --git a/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol b/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol index b679e00..36efced 100644 --- a/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol +++ b/contracts/dao/test/token-factory/interfaces/IERC20Factory.sol @@ -2,5 +2,9 @@ pragma solidity 0.8.16; interface IERC20Factory { - function deployToken(string calldata _name, string calldata _ticker, uint256 _supply) external returns (address); + function deployToken( + string calldata _name, + string calldata _ticker, + uint256 _supply + ) external returns (address); } diff --git a/contracts/dao/test/token-timelock/TokenTimelock.sol b/contracts/dao/test/token-timelock/TokenTimelock.sol index 9b43a84..9a62734 100644 --- a/contracts/dao/test/token-timelock/TokenTimelock.sol +++ b/contracts/dao/test/token-timelock/TokenTimelock.sol @@ -26,7 +26,11 @@ contract TokenTimelock is ITokenTimelock { // timestamp when token release is enabled uint256 private immutable _releaseTime; - constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) { + constructor( + IERC20 token_, + address beneficiary_, + uint256 releaseTime_ + ) { // solhint-disable-next-line require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time"); _token = token_; diff --git a/contracts/dao/test/token-timelock/TokenTimelockFactory.sol b/contracts/dao/test/token-timelock/TokenTimelockFactory.sol index 4f7525a..6f709c4 100644 --- a/contracts/dao/test/token-timelock/TokenTimelockFactory.sol +++ b/contracts/dao/test/token-timelock/TokenTimelockFactory.sol @@ -18,10 +18,12 @@ contract TokenTimelockFactory is ITokenTimelockFactory, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function deployTokenTimelocks( - address[] memory beneficiaries, - uint256[] memory releaseTimes - ) public override onlyRole(DEPLOYER_ROLE) returns (address[] memory) { + function deployTokenTimelocks(address[] memory beneficiaries, uint256[] memory releaseTimes) + public + override + onlyRole(DEPLOYER_ROLE) + returns (address[] memory) + { uint256 length = beneficiaries.length; require(length == releaseTimes.length, "Wrong lengths"); diff --git a/contracts/dao/tokens/ERC20/ERC20.sol b/contracts/dao/tokens/ERC20/ERC20.sol index b1836e2..c6cc727 100644 --- a/contracts/dao/tokens/ERC20/ERC20.sol +++ b/contracts/dao/tokens/ERC20/ERC20.sol @@ -43,7 +43,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { return true; } - function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) { + function transferFrom( + address from, + address to, + uint256 amount + ) external virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -95,7 +99,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { return _allowances[owner][spender]; } - function _transfer(address from, address to, uint256 amount) internal virtual { + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { if (from == address(0)) { revert ERC20TransferFromZeroAddress(); } @@ -154,7 +162,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _afterTokenTransfer(account, address(0), amount); } - function _approve(address owner, address spender, uint256 amount) internal virtual { + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { if (owner == address(0)) { revert ERC20ApproveFromZeroAddress(); } @@ -166,7 +178,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { emit Approval(owner, spender, amount); } - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { if (currentAllowance < amount) { @@ -178,7 +194,15 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } } - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} } diff --git a/contracts/dao/tokens/ERC20/IERC20.sol b/contracts/dao/tokens/ERC20/IERC20.sol index 2b8aef4..7476ba8 100644 --- a/contracts/dao/tokens/ERC20/IERC20.sol +++ b/contracts/dao/tokens/ERC20/IERC20.sol @@ -12,7 +12,11 @@ interface IERC20 { function approve(address spender, uint256 amount) external returns (bool); - function transferFrom(address from, address to, uint256 amount) external returns (bool); + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); diff --git a/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol b/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol index fa21dba..2f429f0 100644 --- a/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol +++ b/contracts/dao/tokens/ERC20/extensions/ERC20Permit.sol @@ -26,7 +26,15 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) EIP712(name_, "1") {} - function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external virtual override { + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external virtual override { // solhint-disable-next-line not-rely-on-time if (block.timestamp > deadline) { revert ExpiredDeadline(); diff --git a/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol b/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol index 825d944..ef1b236 100644 --- a/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol +++ b/contracts/dao/tokens/ERC20/extensions/ERC20Votes.sol @@ -33,7 +33,14 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { _delegate(_msgSender(), delegatee); } - function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external virtual override { + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external virtual override { // solhint-disable-next-line not-rely-on-time if (block.timestamp > expiry) { revert SignatureExpired(); @@ -91,7 +98,11 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); } - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override { + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { super._afterTokenTransfer(from, to, amount); _moveVotingPower(delegates(from), delegates(to), amount); @@ -111,7 +122,11 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { return type(uint224).max; } - function _moveVotingPower(address src, address dst, uint256 amount) private { + function _moveVotingPower( + address src, + address dst, + uint256 amount + ) private { if (src != dst && amount > 0) { if (src != address(0)) { (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount); diff --git a/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol b/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol index dd45b20..8e87e34 100644 --- a/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol +++ b/contracts/dao/tokens/ERC20/extensions/IERC20Permit.sol @@ -5,7 +5,15 @@ pragma solidity 0.8.16; interface IERC20Permit { - function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; function nonces(address owner) external view returns (uint256); diff --git a/contracts/dao/tokens/MainToken.sol b/contracts/dao/tokens/MainToken.sol index ec95974..f3b8072 100644 --- a/contracts/dao/tokens/MainToken.sol +++ b/contracts/dao/tokens/MainToken.sol @@ -6,7 +6,12 @@ pragma solidity 0.8.16; import "./ERC20/ERC20.sol"; contract MainToken is ERC20 { - constructor(string memory name_, string memory symbol_, uint256 totalSupply_, address issuer) ERC20(name_, symbol_) { + constructor( + string memory name_, + string memory symbol_, + uint256 totalSupply_, + address issuer + ) ERC20(name_, symbol_) { _totalSupply = totalSupply_; _balances[issuer] = totalSupply_; diff --git a/contracts/dao/tokens/VMainToken.sol b/contracts/dao/tokens/VMainToken.sol index d2d302f..302ad0d 100644 --- a/contracts/dao/tokens/VMainToken.sol +++ b/contracts/dao/tokens/VMainToken.sol @@ -74,12 +74,20 @@ contract VMainToken is IVMainToken, Pausable, AccessControl, Initializable, ERC2 emit MemberRemovedFromAllowlist(_toRemove); } - function _beforeTokenTransfer(address from, address to, uint256 amount) internal override whenNotPaused { + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal override whenNotPaused { if (!isAllowListed[msg.sender]) revert VMainTokenIsIntransferableUnlessTheSenderIsAllowlisted(); super._beforeTokenTransfer(from, to, amount); } - function _afterTokenTransfer(address from, address to, uint256 amount) internal override { + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal override { super._afterTokenTransfer(from, to, amount); } } diff --git a/contracts/dao/treasury/MultiSigWallet.sol b/contracts/dao/treasury/MultiSigWallet.sol index 906dd26..d026438 100644 --- a/contracts/dao/treasury/MultiSigWallet.sol +++ b/contracts/dao/treasury/MultiSigWallet.sol @@ -138,7 +138,11 @@ contract MultiSigWallet is IMultiSigWallet { _; } - constructor(address[] memory _owners, uint256 _numConfirmationsRequired, address _governor) { + constructor( + address[] memory _owners, + uint256 _numConfirmationsRequired, + address _governor + ) { if (_owners.length > MAX_OWNER_COUNT) { revert OwnersLimitReached(); } @@ -181,7 +185,7 @@ contract MultiSigWallet is IMultiSigWallet { uint256 nConfirmedTxnByOwner = confirmedTransactionsByOwner[owner].length(); - for (uint i = 0; i < nConfirmedTxnByOwner; i++) { + for (uint256 i = 0; i < nConfirmedTxnByOwner; i++) { uint256 _txIndex = confirmedTransactionsByOwner[owner].at(i); Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations -= 1; @@ -191,9 +195,12 @@ contract MultiSigWallet is IMultiSigWallet { emit OwnerRemoval(owner); } - function addOwners( - address[] calldata _owners - ) external override onlyWallet validRequirement(owners.length() + _owners.length, numConfirmationsRequired + _owners.length) { + function addOwners(address[] calldata _owners) + external + override + onlyWallet + validRequirement(owners.length() + _owners.length, numConfirmationsRequired + _owners.length) + { for (uint256 i = 0; i < _owners.length; i++) { address owner = _owners[i]; if (owner == address(0)) { @@ -232,9 +239,15 @@ contract MultiSigWallet is IMultiSigWallet { emit SubmitTransaction(txIndex, msg.sender, _to, _value, _data); } - function confirmTransaction( - uint256 _txIndex - ) external override onlyOwnerOrGov txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) notExpired(_txIndex) { + function confirmTransaction(uint256 _txIndex) + external + override + onlyOwnerOrGov + txExists(_txIndex) + notExecuted(_txIndex) + notConfirmed(_txIndex) + notExpired(_txIndex) + { Transaction storage transaction = transactions[_txIndex]; _requireTargetCodeNotChanged(transaction.to); @@ -285,13 +298,18 @@ contract MultiSigWallet is IMultiSigWallet { return transactions.length; } - function getTransaction( - uint256 _txIndex - ) + function getTransaction(uint256 _txIndex) external view override - returns (address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations, uint256 expireTimestamp) + returns ( + address to, + uint256 value, + bytes memory data, + bool executed, + uint256 numConfirmations, + uint256 expireTimestamp + ) { Transaction memory transaction = transactions[_txIndex]; diff --git a/contracts/dao/treasury/interfaces/IMultiSigWallet.sol b/contracts/dao/treasury/interfaces/IMultiSigWallet.sol index 94c45c7..0fde3f9 100644 --- a/contracts/dao/treasury/interfaces/IMultiSigWallet.sol +++ b/contracts/dao/treasury/interfaces/IMultiSigWallet.sol @@ -22,7 +22,12 @@ interface IMultiSigWallet { function changeRequirement(uint256 _required) external; - function submitTransaction(address _to, uint256 _value, bytes memory _data, uint256 _expireTimestamp) external; + function submitTransaction( + address _to, + uint256 _value, + bytes memory _data, + uint256 _expireTimestamp + ) external; function confirmTransaction(uint256 _txIndex) external; @@ -34,7 +39,14 @@ interface IMultiSigWallet { function getTransactionCount() external returns (uint256); - function getTransaction( - uint256 _txIndex - ) external returns (address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations, uint256 expireTimestamp); + function getTransaction(uint256 _txIndex) + external + returns ( + address to, + uint256 value, + bytes memory data, + bool executed, + uint256 numConfirmations, + uint256 expireTimestamp + ); } From 446c0aa744bba7a73e602513f97b61d106612929 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 14 Apr 2023 12:17:40 +0545 Subject: [PATCH 55/56] removed .js from config path --- scripts/units/DEX/set-fee-to-setter.js | 2 +- scripts/units/DEX/set-fee-to.js | 2 +- scripts/units/add-liquidity-to-pool.js | 2 +- scripts/units/add-liquidity-to-xdc-pool.js | 2 +- scripts/units/create-fixed-lock-on-behalf-of-user.js | 2 +- scripts/units/create_pool_dex.js | 2 +- scripts/units/create_pool_dex_xdc.js | 2 +- scripts/units/create_stablecoin_open_position.js | 2 +- scripts/units/create_stablecoin_proxy_wallet.js | 2 +- scripts/units/execute-proposals.js | 2 +- scripts/units/propose_stream.js | 2 +- scripts/units/setup-multisig-owners.js | 2 +- scripts/units/setup_council_stakes.js | 2 +- scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js | 2 +- scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js | 2 +- .../stablecoin/collateral-pool-config/init-collateral-pool.js | 2 +- scripts/units/stablecoin/collateral-pool-config/set-adapter.js | 2 +- .../stablecoin/collateral-pool-config/set-close-factor-bps.js | 2 +- .../collateral-pool-config/set-debt-accumulated-rate.js | 2 +- .../units/stablecoin/collateral-pool-config/set-debt-ceiling.js | 2 +- .../units/stablecoin/collateral-pool-config/set-debt-floor.js | 2 +- .../stablecoin/collateral-pool-config/set-liquidation-ratio.js | 2 +- .../collateral-pool-config/set-liquidator-incentive-bps.js | 2 +- .../units/stablecoin/collateral-pool-config/set-price-feed.js | 2 +- .../collateral-pool-config/set-price-with-safety-margin.js | 2 +- .../stablecoin/collateral-pool-config/set-stability-fee-rate.js | 2 +- scripts/units/stablecoin/collateral-pool-config/set-strategy.js | 2 +- .../stablecoin/collateral-pool-config/set-total-debt-share.js | 2 +- .../stablecoin/collateral-pool-config/set-treausry-fees-bps.js | 2 +- .../delay-fathom-oracle-price-feed/delay-price-feed-pause.js | 2 +- .../delay-fathom-oracle-price-feed/delay-price-feed-unpause.js | 2 +- .../delay-fathom-oracle-price-feed/set-access-control-config.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-oracle.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-price-life.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-price.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-token-one.js | 2 +- .../stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js | 2 +- scripts/units/stablecoin/flash-mint-module/set-fee-rate.js | 2 +- scripts/units/stablecoin/position-manager/set-price-oracle.js | 2 +- scripts/units/stablecoin/position-manager/stableswap-pause.js | 2 +- scripts/units/stablecoin/position-manager/stableswap-unpause.js | 2 +- scripts/units/stablecoin/price-oracle/price-oracle-cage.js | 2 +- scripts/units/stablecoin/price-oracle/price-oracle-pause.js | 2 +- scripts/units/stablecoin/price-oracle/price-oracle-uncage.js | 2 +- scripts/units/stablecoin/price-oracle/price-oracle-unpause.js | 2 +- scripts/units/stablecoin/price-oracle/set-price.js | 2 +- .../stablecoin/price-oracle/set-stable-coin-reference-price.js | 2 +- .../units/stablecoin/proxy-actions-storage/set-proxy-action.js | 2 +- scripts/units/stablecoin/showstopper/cage-pool.js | 2 +- scripts/units/stablecoin/showstopper/cage-showstopper.js | 2 +- scripts/units/stablecoin/showstopper/set-book-keeper.js | 2 +- scripts/units/stablecoin/showstopper/set-cage-cool-down.js | 2 +- scripts/units/stablecoin/showstopper/set-liquidation-engine.js | 2 +- scripts/units/stablecoin/showstopper/set-price-oracle.js | 2 +- scripts/units/stablecoin/showstopper/set-system-debt-engine.js | 2 +- .../stablecoin/stability-fee-collector/collect_stability_fee.js | 2 +- .../stability-fee-collector/set-system-debt-engine.js | 2 +- .../stablecoin/stability-fee-collector/stability-fee-pause.js | 2 +- .../stablecoin/stability-fee-collector/stability-fee-unpause.js | 2 +- scripts/units/stablecoin/stableswap/emergency-withdraw.js | 2 +- scripts/units/stablecoin/stableswap/set-fee-in.js | 2 +- scripts/units/stablecoin/stableswap/set-fee-out.js | 2 +- scripts/units/stablecoin/stableswap/stableswap-pause.js | 2 +- scripts/units/stablecoin/stableswap/stableswap-unpause.js | 2 +- scripts/units/stablecoin/stableswap/withdraw-fees.js | 2 +- .../units/stablecoin/system-debt-engine/set-surplus-buffer.js | 2 +- .../stablecoin/system-debt-engine/system-debt-engine-pause.js | 2 +- .../stablecoin/system-debt-engine/system-debt-engine-unpause.js | 2 +- .../system-debt-engine/withdraw-collateral-surplus.js | 2 +- .../system-debt-engine/withdraw-stablecoin-surplus.js | 2 +- scripts/units/stableswap-setup.js | 2 +- scripts/units/swap-Exact-ETH-For-Tokens.js | 2 +- scripts/units/swap-Exact-Tokens-For-Tokens.js | 2 +- scripts/units/swap-Tokens-For-Exact-ETH.js | 2 +- 75 files changed, 75 insertions(+), 75 deletions(-) diff --git a/scripts/units/DEX/set-fee-to-setter.js b/scripts/units/DEX/set-fee-to-setter.js index edc7e1d..006f9da 100644 --- a/scripts/units/DEX/set-fee-to-setter.js +++ b/scripts/units/DEX/set-fee-to-setter.js @@ -1,6 +1,6 @@ const fs = require('fs'); const txnHelper = require('../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../config/config.js') +const addressesConfig = require('../../../config/config') const DEX_FACTORY_ADDRESS =addressesConfig.DEX_FACTORY_ADDRESS const FEE_TO_SETTER = "" diff --git a/scripts/units/DEX/set-fee-to.js b/scripts/units/DEX/set-fee-to.js index 3d9469a..b34f485 100644 --- a/scripts/units/DEX/set-fee-to.js +++ b/scripts/units/DEX/set-fee-to.js @@ -1,6 +1,6 @@ const fs = require('fs'); const txnHelper = require('../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../config/config.js') +const addressesConfig = require('../../../config/config') const DEX_FACTORY_ADDRESS =addressesConfig.DEX_FACTORY_ADDRESS const TO_BE_WHITELISTED = "0x" diff --git a/scripts/units/add-liquidity-to-pool.js b/scripts/units/add-liquidity-to-pool.js index 62a9cb5..8e311cc 100644 --- a/scripts/units/add-liquidity-to-pool.js +++ b/scripts/units/add-liquidity-to-pool.js @@ -8,7 +8,7 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const Token_A_Address = addressesConfig.WETH_ADDRESS // SET AS Necessary const Token_B_Address = addressesConfig.FXD_ADDRESS// SET AS Necessary diff --git a/scripts/units/add-liquidity-to-xdc-pool.js b/scripts/units/add-liquidity-to-xdc-pool.js index be49d7f..19224d3 100644 --- a/scripts/units/add-liquidity-to-xdc-pool.js +++ b/scripts/units/add-liquidity-to-xdc-pool.js @@ -6,7 +6,7 @@ const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol" const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = addresses.fthmToken //FTHM address diff --git a/scripts/units/create-fixed-lock-on-behalf-of-user.js b/scripts/units/create-fixed-lock-on-behalf-of-user.js index 22d44a5..67face2 100644 --- a/scripts/units/create-fixed-lock-on-behalf-of-user.js +++ b/scripts/units/create-fixed-lock-on-behalf-of-user.js @@ -5,7 +5,7 @@ const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const LOCK_PERIOD = 365 * 24 * 60 * 60; //SET AS NEEDED diff --git a/scripts/units/create_pool_dex.js b/scripts/units/create_pool_dex.js index 9b2351a..7fab806 100644 --- a/scripts/units/create_pool_dex.js +++ b/scripts/units/create_pool_dex.js @@ -9,7 +9,7 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const Token_A_Address = addressesConfig.USD_ADDRESS //USD diff --git a/scripts/units/create_pool_dex_xdc.js b/scripts/units/create_pool_dex_xdc.js index 0bcc63b..964c816 100644 --- a/scripts/units/create_pool_dex_xdc.js +++ b/scripts/units/create_pool_dex_xdc.js @@ -5,7 +5,7 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const TOKEN_ADDRESS = addresses.fthmToken //FTHM address const AMOUNT_TOKEN_DESIRED = web3.utils.toWei('2', 'ether') diff --git a/scripts/units/create_stablecoin_open_position.js b/scripts/units/create_stablecoin_open_position.js index 4b96ec3..ffb592e 100644 --- a/scripts/units/create_stablecoin_open_position.js +++ b/scripts/units/create_stablecoin_open_position.js @@ -11,7 +11,7 @@ const rawDataStablecoin = fs.readFileSync('../../config/stablecoin-addresses-pro const addressesStableCoin = JSON.parse(rawDataStablecoin); const XDC_COL = web3.utils.toWei('20','ether') -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const PROXY_WALLET = addressesStableCoin.proxyWallet diff --git a/scripts/units/create_stablecoin_proxy_wallet.js b/scripts/units/create_stablecoin_proxy_wallet.js index 87059eb..c9f2822 100644 --- a/scripts/units/create_stablecoin_proxy_wallet.js +++ b/scripts/units/create_stablecoin_proxy_wallet.js @@ -10,7 +10,7 @@ const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); const IProxyRegistry = artifacts.require("./dao/test/stablecoin/IProxyRegistry.sol"); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const PROXY_WALLET_REGISTRY_ADDRESS = addressesConfig.PROXY_WALLET_REGISTRY_ADDRESS const _encodeBuildFunction = (_account) => { diff --git a/scripts/units/execute-proposals.js b/scripts/units/execute-proposals.js index 8f44931..b87bf0e 100644 --- a/scripts/units/execute-proposals.js +++ b/scripts/units/execute-proposals.js @@ -8,7 +8,7 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') //SET VALUE AS HOW MUCH ETH YOU WANT TO SPEND FOR THE WHOLE TRANSACTION(msg.value) const value = constants.EMPTY_BYTES; diff --git a/scripts/units/propose_stream.js b/scripts/units/propose_stream.js index b1e810f..262fad4 100644 --- a/scripts/units/propose_stream.js +++ b/scripts/units/propose_stream.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnSaver = require('./helpers/transactionSaver') const txnHelper = require('./helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const eventsHelper = require("../tests/helpers/eventsHelper"); const constants = require('./helpers/constants') diff --git a/scripts/units/setup-multisig-owners.js b/scripts/units/setup-multisig-owners.js index 763020f..078801b 100644 --- a/scripts/units/setup-multisig-owners.js +++ b/scripts/units/setup-multisig-owners.js @@ -2,7 +2,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); diff --git a/scripts/units/setup_council_stakes.js b/scripts/units/setup_council_stakes.js index 1e6d459..b8c5a2d 100644 --- a/scripts/units/setup_council_stakes.js +++ b/scripts/units/setup_council_stakes.js @@ -5,7 +5,7 @@ const constants = require('./helpers/constants') const txnHelper = require('./helpers/submitAndExecuteTransaction') const IStaking = artifacts.require('./dao/staking/interfaces/IStaking.sol'); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const LOCK_PERIOD = 365 * 24 * 60 * 60; //SET AS NEEDED diff --git a/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js index 0c495e4..b121880 100644 --- a/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/allowlist-bookkeeper.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const BOOK_KEEPER_ADDRESS =addressesConfig.BOOK_KEEPER_ADDRESS const TO_BE_ALLOWLISTED = "0x" diff --git a/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js index a4ea501..db3b75c 100644 --- a/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js +++ b/scripts/units/stablecoin/book-keeper/blocklist-bookkeeper.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const BOOK_KEEPER_ADDRESS =addressesConfig.BOOK_KEEPER_ADDRESS const TO_BE_BLOCKLISTED = "0x" diff --git a/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js index 44eb65c..a5300b9 100644 --- a/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js +++ b/scripts/units/stablecoin/collateral-pool-config/init-collateral-pool.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const COLLATERAL_POOL_ID = '' diff --git a/scripts/units/stablecoin/collateral-pool-config/set-adapter.js b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js index 1ff78b3..7807281 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-adapter.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-adapter.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const ADAPTER = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js index 791af94..4433ad3 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-close-factor-bps.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const CLOSE_FACTOR_BPS = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js index 4a3d657..1b76c25 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-accumulated-rate.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const DEBT_ACCUMULATED_RATE = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js index cde405b..37cd9f9 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-ceiling.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const DEBT_CEILING = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js index 7c81907..22c7801 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-debt-floor.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const DEBT_FLOOR = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js index 39a4541..01eeb64 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidation-ratio.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const DATA = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js index 670c8ee..4a9cd77 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-liquidator-incentive-bps.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const LIQUIDATOR_INCENTIVE_BPS = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js index b746fb8..9ae4e0b 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-feed.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const PRICE_FEED = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js index 9ee5ec5..f9461fe 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-price-with-safety-margin.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const PRICE_WITH_SAFETY_MARGIN = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js index 5a8e009..574a4bc 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-stability-fee-rate.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const STABILITY_FEE = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-strategy.js b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js index 56be663..fb1d2ba 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-strategy.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-strategy.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STRATEGY = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS const _encodeSetStrategy = (_collateralPoolId, _strategy) => { diff --git a/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js index 0cd3317..37b69bb 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-total-debt-share.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const TOTAL_DEBT_SHARE = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js index a912ef3..243970c 100644 --- a/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js +++ b/scripts/units/stablecoin/collateral-pool-config/set-treausry-fees-bps.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const TREASURY_FEES_BPS = '' const COLLATERAL_POOL_CONFIG_ADDRESS =addressesConfig.COLLATERAL_POOL_CONFIG_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js index a681c47..2f3e6fc 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js index 4cff8fd..0997c12 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/delay-price-feed-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js index 5cfa6d1..16245cd 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-access-control-config.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const ACCESS_CONTROL_CONFIG = "" const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js index d1ef95f..309d5d1 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-oracle.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const ORACLE = "" const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js index 8cb9b2a..eba734f 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price-life.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SECOND = 1 const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js index a025c4e..fb2b36b 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-price.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js index bc75871..0e92a1d 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-time-delay.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SECOND = 1 const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js index 0580e8d..7667740 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-one.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const TOKEN_ONE = "" const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js index ab90d82..1a725ab 100644 --- a/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js +++ b/scripts/units/stablecoin/delay-fathom-oracle-price-feed/set-token-zero.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const TOKEN_ZERO = "" const DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS =addressesConfig.DELAY_FATHOM_ORACLE_PRICE_FEED_ADDRESS diff --git a/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js index 1aee576..4c66f13 100644 --- a/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js +++ b/scripts/units/stablecoin/flash-mint-module/set-fee-rate.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const FEE_RATE =1 const FLASH_MINT_MODULE_ADDRESS = addressesConfig.FLASH_MINT_MODULE_ADDRESS diff --git a/scripts/units/stablecoin/position-manager/set-price-oracle.js b/scripts/units/stablecoin/position-manager/set-price-oracle.js index 91c6431..a3656d0 100644 --- a/scripts/units/stablecoin/position-manager/set-price-oracle.js +++ b/scripts/units/stablecoin/position-manager/set-price-oracle.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE = "" const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS diff --git a/scripts/units/stablecoin/position-manager/stableswap-pause.js b/scripts/units/stablecoin/position-manager/stableswap-pause.js index 421cc01..b1af0cd 100644 --- a/scripts/units/stablecoin/position-manager/stableswap-pause.js +++ b/scripts/units/stablecoin/position-manager/stableswap-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS diff --git a/scripts/units/stablecoin/position-manager/stableswap-unpause.js b/scripts/units/stablecoin/position-manager/stableswap-unpause.js index b3ae5f1..d2c06c9 100644 --- a/scripts/units/stablecoin/position-manager/stableswap-unpause.js +++ b/scripts/units/stablecoin/position-manager/stableswap-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const POSITION_MANAGER_ADDRESS =addressesConfig.POSITION_MANAGER_ADDRESS diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-cage.js b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js index a8ab51a..d46b8ca 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-cage.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-cage.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-pause.js b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js index bb876a2..88aa223 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-pause.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js index f1d6ec0..d471b0a 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-uncage.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS diff --git a/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js index 394795e..145e77b 100644 --- a/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js +++ b/scripts/units/stablecoin/price-oracle/price-oracle-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS diff --git a/scripts/units/stablecoin/price-oracle/set-price.js b/scripts/units/stablecoin/price-oracle/set-price.js index fc8caba..783faec 100644 --- a/scripts/units/stablecoin/price-oracle/set-price.js +++ b/scripts/units/stablecoin/price-oracle/set-price.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const COLLATERAL_POOL_ID = 123 diff --git a/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js index df5ef13..7fb87d7 100644 --- a/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js +++ b/scripts/units/stablecoin/price-oracle/set-stable-coin-reference-price.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE_ADDRESS =addressesConfig.PRICE_ORACLE_ADDRESS const DATA = 123 diff --git a/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js index a511a0f..e53b66c 100644 --- a/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js +++ b/scripts/units/stablecoin/proxy-actions-storage/set-proxy-action.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PROXY_ACTION_STORAGE_ADDRESS =addressesConfig.PROXY_ACTION_STORAGE_ADDRESS const PROXY_ACTION_ADDRESS = "0x" diff --git a/scripts/units/stablecoin/showstopper/cage-pool.js b/scripts/units/stablecoin/showstopper/cage-pool.js index 4908832..f3f7a72 100644 --- a/scripts/units/stablecoin/showstopper/cage-pool.js +++ b/scripts/units/stablecoin/showstopper/cage-pool.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const COLLATERAL_POOL_ID = '' const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/cage-showstopper.js b/scripts/units/stablecoin/showstopper/cage-showstopper.js index 1f9f99a..381d80e 100644 --- a/scripts/units/stablecoin/showstopper/cage-showstopper.js +++ b/scripts/units/stablecoin/showstopper/cage-showstopper.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/set-book-keeper.js b/scripts/units/stablecoin/showstopper/set-book-keeper.js index c04fdad..0dc4acb 100644 --- a/scripts/units/stablecoin/showstopper/set-book-keeper.js +++ b/scripts/units/stablecoin/showstopper/set-book-keeper.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const BOOK_KEEPER_ADDRESS = "" const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/set-cage-cool-down.js b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js index 041cfcf..4e80cc0 100644 --- a/scripts/units/stablecoin/showstopper/set-cage-cool-down.js +++ b/scripts/units/stablecoin/showstopper/set-cage-cool-down.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const CAGE_COOL_DOWN =1 const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/set-liquidation-engine.js b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js index b71d245..2aec56f 100644 --- a/scripts/units/stablecoin/showstopper/set-liquidation-engine.js +++ b/scripts/units/stablecoin/showstopper/set-liquidation-engine.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const LIQUIDATION_ENGINE_ADDRESS = "" const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/set-price-oracle.js b/scripts/units/stablecoin/showstopper/set-price-oracle.js index c100ecf..c3c0a46 100644 --- a/scripts/units/stablecoin/showstopper/set-price-oracle.js +++ b/scripts/units/stablecoin/showstopper/set-price-oracle.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const PRICE_ORACLE = "" const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/showstopper/set-system-debt-engine.js b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js index b1161e2..883073c 100644 --- a/scripts/units/stablecoin/showstopper/set-system-debt-engine.js +++ b/scripts/units/stablecoin/showstopper/set-system-debt-engine.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SYSTEM_DEBT_ENGINE = "" const SHOW_STOPPER_ADDRESS =addressesConfig.SHOW_STOPPER_ADDRESS diff --git a/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js index 31f4806..335ced9 100644 --- a/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js +++ b/scripts/units/stablecoin/stability-fee-collector/collect_stability_fee.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS diff --git a/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js index 4da93c3..b5560ed 100644 --- a/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js +++ b/scripts/units/stablecoin/stability-fee-collector/set-system-debt-engine.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SYSTEM_DEBT_ENGINE_ADDRESS = "" const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js index 4928b21..6b550b1 100644 --- a/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS diff --git a/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js index fec798e..9fe27cb 100644 --- a/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js +++ b/scripts/units/stablecoin/stability-fee-collector/stability-fee-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STABILITY_FEE_COLLECTOR_ADDRESS =addressesConfig.STABILITY_FEE_COLLECTOR_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/emergency-withdraw.js b/scripts/units/stablecoin/stableswap/emergency-withdraw.js index 5e94ec9..04375c1 100644 --- a/scripts/units/stablecoin/stableswap/emergency-withdraw.js +++ b/scripts/units/stablecoin/stableswap/emergency-withdraw.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const ACCOUNT_DESTINATION = "" const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/set-fee-in.js b/scripts/units/stablecoin/stableswap/set-fee-in.js index 91112b8..20065de 100644 --- a/scripts/units/stablecoin/stableswap/set-fee-in.js +++ b/scripts/units/stablecoin/stableswap/set-fee-in.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const FEE_IN = 1 const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/set-fee-out.js b/scripts/units/stablecoin/stableswap/set-fee-out.js index 683cb58..173a184 100644 --- a/scripts/units/stablecoin/stableswap/set-fee-out.js +++ b/scripts/units/stablecoin/stableswap/set-fee-out.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const FEE_OUT = 1 const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/stableswap-pause.js b/scripts/units/stablecoin/stableswap/stableswap-pause.js index dcd062d..7d3479d 100644 --- a/scripts/units/stablecoin/stableswap/stableswap-pause.js +++ b/scripts/units/stablecoin/stableswap/stableswap-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/stableswap-unpause.js b/scripts/units/stablecoin/stableswap/stableswap-unpause.js index a4cae7b..a00a185 100644 --- a/scripts/units/stablecoin/stableswap/stableswap-unpause.js +++ b/scripts/units/stablecoin/stableswap/stableswap-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/stableswap/withdraw-fees.js b/scripts/units/stablecoin/stableswap/withdraw-fees.js index f1ce7f0..f7fdd2c 100644 --- a/scripts/units/stablecoin/stableswap/withdraw-fees.js +++ b/scripts/units/stablecoin/stableswap/withdraw-fees.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const _destination = "" const STABLE_SWAP_ADDRESS =addressesConfig.STABLE_SWAP_ADDRESS diff --git a/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js index 040ffc6..fe9f5d1 100644 --- a/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js +++ b/scripts/units/stablecoin/system-debt-engine/set-surplus-buffer.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const DATA = 1 const SYSTEM_DEBT_ENGINE_ADDRESS = addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS diff --git a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js index 8f921f2..2e5e24e 100644 --- a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js +++ b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-pause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS diff --git a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js index 98db800..bb92bf3 100644 --- a/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js +++ b/scripts/units/stablecoin/system-debt-engine/system-debt-engine-unpause.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS diff --git a/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js index bcab14c..588d5aa 100644 --- a/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js +++ b/scripts/units/stablecoin/system-debt-engine/withdraw-collateral-surplus.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRSESS const COLLATERAL_POOL_ID = '' diff --git a/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js index 31cc281..c55966f 100644 --- a/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js +++ b/scripts/units/stablecoin/system-debt-engine/withdraw-stablecoin-surplus.js @@ -1,7 +1,7 @@ const fs = require('fs'); const txnHelper = require('../../helpers/submitAndExecuteTransaction') -const addressesConfig = require('../../../../config/config.js') +const addressesConfig = require('../../../../config/config') const TO = "0x" const VALUE = 123 const SYSTEM_DEBT_ENGINE_ADDRESS =addressesConfig.SYSTEM_DEBT_ENGINE_ADDRESS diff --git a/scripts/units/stableswap-setup.js b/scripts/units/stableswap-setup.js index 0baf210..3a834fd 100644 --- a/scripts/units/stableswap-setup.js +++ b/scripts/units/stableswap-setup.js @@ -9,7 +9,7 @@ const IMultiSigWallet = artifacts.require("./dao/treasury/interfaces/IMultiSigWa const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const STABLE_SWAP_ADDRESS = addressesConfig.STABLE_SWAP_ADDRESS const USDAddress = addressesConfig.USD_ADDRESS const FXDAddress = addressesConfig.FXD_ADDRESS diff --git a/scripts/units/swap-Exact-ETH-For-Tokens.js b/scripts/units/swap-Exact-ETH-For-Tokens.js index 8231ba5..e9df8f8 100644 --- a/scripts/units/swap-Exact-ETH-For-Tokens.js +++ b/scripts/units/swap-Exact-ETH-For-Tokens.js @@ -9,7 +9,7 @@ const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = addresses.fthmToken //fthm diff --git a/scripts/units/swap-Exact-Tokens-For-Tokens.js b/scripts/units/swap-Exact-Tokens-For-Tokens.js index e919b09..c11f6fa 100644 --- a/scripts/units/swap-Exact-Tokens-For-Tokens.js +++ b/scripts/units/swap-Exact-Tokens-For-Tokens.js @@ -9,7 +9,7 @@ const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const Token_A_Address = addressesConfig.USD_ADDRESS //USD+ const Token_B_Address = addressesConfig.WETH_ADDRESS //WXDC diff --git a/scripts/units/swap-Tokens-For-Exact-ETH.js b/scripts/units/swap-Tokens-For-Exact-ETH.js index fe86683..9939059 100644 --- a/scripts/units/swap-Tokens-For-Exact-ETH.js +++ b/scripts/units/swap-Tokens-For-Exact-ETH.js @@ -9,7 +9,7 @@ const addresses = JSON.parse(rawdata); const IUniswapRouter = artifacts.require("./dao/test/dex/IUniswapV2Router01.sol"); -const addressesConfig = require('../../config/config.js') +const addressesConfig = require('../../config/config') const WETH_ADDRESS = addressesConfig.WETH_ADDRESS const TOKEN_ADDRESS = addressesConfig.USD_ADDRESS From 8d480d4837bd905bbcb926cc2aeafc7235c0b2a4 Mon Sep 17 00:00:00 2001 From: ssubik Date: Fri, 14 Apr 2023 12:27:27 +0545 Subject: [PATCH 56/56] adding more data save --- .../helpers/submitAndExecuteTransaction.js | 7 ++++++- scripts/units/helpers/transactionSaver.js | 20 +++++++++++++++---- scripts/units/transfer-tokens.js | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/scripts/units/helpers/submitAndExecuteTransaction.js b/scripts/units/helpers/submitAndExecuteTransaction.js index 83fb146..3351ea3 100644 --- a/scripts/units/helpers/submitAndExecuteTransaction.js +++ b/scripts/units/helpers/submitAndExecuteTransaction.js @@ -44,7 +44,12 @@ async function submitAndExecute(encodedFunction, targetAddress, TransactionName, console.log(`Transaction executed successfully for ${TransactionName}. TxHash: ${resultExecuteTransaction.transactionHash}`); } - await txnSaver.saveTxnIndex(TransactionName,tx) + await txnSaver.saveTxnIndex( + TransactionName, + tx, + resultSubmitTransaction.transactionHash, + resultConfirmTransaction.transactionHash, + resultExecuteTransaction.transactionHash) } await _submitAndExecute() diff --git a/scripts/units/helpers/transactionSaver.js b/scripts/units/helpers/transactionSaver.js index 1b0a643..7def30b 100644 --- a/scripts/units/helpers/transactionSaver.js +++ b/scripts/units/helpers/transactionSaver.js @@ -23,7 +23,10 @@ const constants = require('./constants') async function saveTxnIndex( TransactionName, - txnIndex + txnIndex, + submitTransactionHash, + confirmationTransactionHash, + executeTransactionHash ) { let newTxnStore; @@ -35,7 +38,10 @@ async function saveTxnIndex( [ { "id":1, - "txnIndex": txnIndex + "txnIndex": txnIndex, + "submitTransactionHash": submitTransactionHash, + "confirmationTransactionHash": confirmationTransactionHash, + "executeTransactionHash": executeTransactionHash } ] newTxnStore = object @@ -46,7 +52,10 @@ async function saveTxnIndex( object[TransactionName].push( { "id": object[TransactionName].length+1, - "txnIndex": txnIndex + "txnIndex": txnIndex, + "submitTransactionHash": submitTransactionHash, + "confirmationTransactionHash": confirmationTransactionHash, + "executeTransactionHash": executeTransactionHash } ) }else{ @@ -54,7 +63,10 @@ async function saveTxnIndex( object[TransactionName] = [ { "id":1, - "txnIndex": txnIndex + "txnIndex": txnIndex, + "submitTransactionHash": submitTransactionHash, + "confirmationTransactionHash": confirmationTransactionHash, + "executeTransactionHash": executeTransactionHash } ] } diff --git a/scripts/units/transfer-tokens.js b/scripts/units/transfer-tokens.js index d963300..b701988 100644 --- a/scripts/units/transfer-tokens.js +++ b/scripts/units/transfer-tokens.js @@ -2,7 +2,7 @@ const fs = require('fs'); const constants = require('./helpers/constants') const T_TO_TRANSFER_PLACEHOLDER = web3.utils.toWei('10000000','ether') //SET AS NEEDED -const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0xd32Cd592c5296e893AfF7eb8518977A67e4b6741" //SET AS NEEDED +const TRANSFER_TO_ACCOUNT_PLACEHOLDER = "0x0Eb7DEE6e18Cce8fE839E986502d95d47dC0ADa3" //SET AS NEEDED const txnHelper = require('./helpers/submitAndExecuteTransaction') const rawdata = fs.readFileSync(constants.PATH_TO_ADDRESSES); const addresses = JSON.parse(rawdata);