Repository: Synthetixio/synthetix-v3 (ts)

Issue:
Write utility to print contract sizes, and add test in CI that fails if any contract is oversized

Pull request description:
Add hardhat plugin for contract sizes
Fix #550

The plugin [hardhat-contract-sizes](https://github.com/ItsNickBarry/hardhat-contract-sizer) was added to the packages:
* core-contracts
* core-modules
* synthetix-main

Which adds a new `npx hardhat size-contracts` hardhat task.

This PR also adds GA tests for checking if any contract is oversize in CI.

FYI, all contracts appear to be very small rn.

Patch:
diff --git a/.github/workflows/contract-size.yml b/.github/workflows/contract-size.yml
new file mode 100644
index 00000000..b0663e70
--- /dev/null
+++ b/.github/workflows/contract-size.yml
@@ -0,0 +1,23 @@
+name: Contract sizes
+
+on: [push]
+
+jobs:
+  size:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        package:
+          [core-contracts, core-modules, synthetix-main]
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: "16.8.0"
+          cache: "npm"
+      - run: npm ci
+      - name: Compile contracts
+        run: npm run --workspace=./packages/${{ matrix.package }} compile-contracts
+      - name: Execute size command
+        run: npm run --workspace=./packages/${{ matrix.package }} size-contracts
diff --git a/package-lock.json b/package-lock.json
index d8883865..f8a9a172 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2637,6 +2637,22 @@
         "node": ">=4"
       }
     },
+    "node_modules/cli-table3": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz",
+      "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
+      "dev": true,
+      "dependencies": {
+        "object-assign": "^4.1.0",
+        "string-width": "^4.2.0"
+      },
+      "engines": {
+        "node": "10.* || >= 12.*"
+      },
+      "optionalDependencies": {
+        "colors": "^1.1.2"
+      }
+    },
     "node_modules/cli-width": {
       "version": "2.2.1",
       "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
@@ -2719,6 +2735,15 @@
       "version": "1.1.4",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
     },
+    "node_modules/colors": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.1.90"
+      }
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
@@ -6152,6 +6177,19 @@
         "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
       }
     },
+    "node_modules/hardhat-contract-sizer": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/hardhat-contract-sizer/-/hardhat-contract-sizer-2.1.1.tgz",
+      "integrity": "sha512-QgfuwdUkKT7Ugn6Zja26Eie7h6OLcBfWBewaaQtYMCzyglNafQPgUIznN2C42/iFmGrlqFPbqv4B98Iev89KSQ==",
+      "dev": true,
+      "dependencies": {
+        "cli-table3": "^0.6.0",
+        "colors": "^1.4.0"
+      },
+      "peerDependencies": {
+        "hardhat": "^2.0.0"
+      }
+    },
     "node_modules/hardhat/node_modules/@solidity-parser/parser": {
       "version": "0.14.0",
       "integrity": "sha512-cX0JJRcmPtNUJpzD2K7FdA7qQsTOk1UZnFx2k7qAg9ZRvuaH5NBe5IEdBMXGlmf2+FmjhqbygJ26H8l2SV7aKQ==",
@@ -12867,6 +12905,7 @@
       "devDependencies": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "2.1.1",
         "solidity-coverage": "0.7.17"
       }
     },
@@ -13218,6 +13257,7 @@
       "devDependencies": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "2.1.1",
         "solidity-coverage": "0.7.17"
       }
     },
@@ -13573,6 +13613,7 @@
       "devDependencies": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "2.1.1",
         "solidity-coverage": "0.7.17"
       }
     }
@@ -14626,6 +14667,7 @@
       "requires": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "*",
         "solidity-coverage": "0.7.17"
       }
     },
@@ -14873,6 +14915,7 @@
       "requires": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "2.1.1",
         "solidity-coverage": "0.7.17"
       }
     },
@@ -15119,6 +15162,7 @@
       "requires": {
         "@nomiclabs/hardhat-ethers": "2.0.2",
         "hardhat": "2.6.8",
+        "hardhat-contract-sizer": "2.1.1",
         "solidity-coverage": "0.7.17"
       }
     },
@@ -16006,6 +16050,17 @@
         "restore-cursor": "^2.0.0"
       }
     },
+    "cli-table3": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz",
+      "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
+      "dev": true,
+      "requires": {
+        "colors": "^1.1.2",
+        "object-assign": "^4.1.0",
+        "string-width": "^4.2.0"
+      }
+    },
     "cli-width": {
       "version": "2.2.1",
       "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
@@ -16075,6 +16130,12 @@
       "version": "1.1.4",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
     },
+    "colors": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+      "dev": true
+    },
     "combined-stream": {
       "version": "1.0.8",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
@@ -18820,6 +18881,16 @@
         }
       }
     },
+    "hardhat-contract-sizer": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/hardhat-contract-sizer/-/hardhat-contract-sizer-2.1.1.tgz",
+      "integrity": "sha512-QgfuwdUkKT7Ugn6Zja26Eie7h6OLcBfWBewaaQtYMCzyglNafQPgUIznN2C42/iFmGrlqFPbqv4B98Iev89KSQ==",
+      "dev": true,
+      "requires": {
+        "cli-table3": "^0.6.0",
+        "colors": "^1.4.0"
+      }
+    },
     "has": {
       "version": "1.0.3",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
diff --git a/packages/cli/internal/signatures.js b/packages/cli/internal/signatures.js
index 4a57cbc1..79cf3d04 100644
--- a/packages/cli/internal/signatures.js
+++ b/packages/cli/internal/signatures.js
@@ -1,4 +1,4 @@
-function getSignatureWithParameterNamesAndValues(contractName, functionName, functionParameters) {
+function getFullFunctionSignature(contractName, functionName, functionParameters) {
   const abi = hre.deployer.deployment.abis[contractName];
   const functionAbi = abi.find((abiItem) => abiItem.name === functionName);
 
@@ -13,13 +13,33 @@ function getSignatureWithParameterNamesAndValues(contractName, functionName, fun
     parameterDescriptions.push(`${input.type} ${input.name}${valueDescription}`);
   }
 
-  let str = `${contractName}.${functionAbi.name}${multiline ? '(\n' : '('}`;
+  let str = `${functionAbi.name}${multiline ? '(\n' : '('}`;
   str += `${multiline ? '  ' : ''}${parameterDescriptions.join(multiline ? ',\n  ' : ', ')}`;
   str += `${multiline ? '\n)' : ')'}`;
 
   return str;
 }
 
+function getFullEventSignature(contractName, event) {
+  const abi = hre.deployer.deployment.abis[contractName];
+  const eventAbi = abi.find((entry) => entry.name === event.event);
+
+  let i = 0;
+  const namedArgs = event.args.map((arg) => {
+    const input = eventAbi.inputs[i];
+    i++;
+
+    return `${input.type} ${input.name} = ${arg}`;
+  });
+
+  let str = `${event.event}(\n`;
+  str += `  ${namedArgs.join(',\n  ')}`;
+  str += '\n)';
+
+  return str;
+}
+
 module.exports = {
-  getSignatureWithParameterNamesAndValues,
+  getFullFunctionSignature,
+  getFullEventSignature,
 };
diff --git a/packages/cli/subtasks/execute-call.js b/packages/cli/subtasks/execute-call.js
index 9b201a73..dbb0251e 100644
--- a/packages/cli/subtasks/execute-call.js
+++ b/packages/cli/subtasks/execute-call.js
@@ -3,49 +3,53 @@ const { SUBTASK_EXECUTE_CALL } = require('../task-names');
 const logger = require('@synthetixio/core-js/utils/logger');
 const chalk = require('chalk');
 const prompter = require('@synthetixio/core-js/utils/prompter');
-const { getSignatureWithParameterNamesAndValues } = require('../internal/signatures');
+const { getFullFunctionSignature, getFullEventSignature } = require('../internal/signatures');
 
 subtask(SUBTASK_EXECUTE_CALL, 'Execute the current tx').setAction(async (taskArguments, hre) => {
-  logger.info(
-    `Calling ${hre.cli.contractName}.${getSignatureWithParameterNamesAndValues(
+  const target = hre.deployer.deployment.general.contracts[hre.cli.contractName];
+  const address = target.proxyAddress || target.deployedAddress;
+
+  logger.notice(
+    `${hre.cli.contractName}.${getFullFunctionSignature(
       hre.cli.contractName,
       hre.cli.functionName,
       hre.cli.functionParameters
     )}`
   );
-
-  const target = hre.deployer.deployment.general.contracts[hre.cli.contractName];
-  const address = target.proxyAddress || target.deployedAddress;
+  logger.info(`Target: ${address}`);
 
   const abi = hre.deployer.deployment.abis[hre.cli.contractName];
   const functionAbi = abi.find((abiItem) => abiItem.name === hre.cli.functionName);
 
+  const contract = new hre.ethers.Contract(address, abi, hre.ethers.provider);
+  const tx = await contract.populateTransaction[hre.cli.functionName](
+    ...hre.cli.functionParameters
+  );
+  logger.info(`Calldata: ${tx.data}`);
+
   const readOnly = functionAbi.stateMutability === 'view';
   if (readOnly) {
-    await executeReadTransaction(address, abi);
+    await executeReadTransaction(contract);
   } else {
-    await executeWriteTransaction(address, abi);
+    await executeWriteTransaction(contract);
   }
 
   hre.cli.functionParameters = null;
   hre.cli.functionName = null;
 });
 
-async function executeReadTransaction(address, abi) {
-  const contract = new hre.ethers.Contract(address, abi, hre.ethers.provider);
-
+async function executeReadTransaction(contract) {
   const result = await contract[hre.cli.functionName](...hre.cli.functionParameters);
 
-  logger.checked(`Result: ${result}`);
+  console.log(chalk.green(`✓ ${result}`));
 }
 
-async function executeWriteTransaction(address, abi) {
-  logger.warn('This is a write transaction');
-
+async function executeWriteTransaction(contract) {
   const signer = (await hre.ethers.getSigners())[0];
-  logger.info(`Signer to use: ${signer.address}`);
+  logger.info(`Signer: ${signer.address}`);
+  logger.warn('This is a write transaction!');
 
-  const contract = new hre.ethers.Contract(address, abi, signer);
+  contract = contract.connect(signer);
 
   let tx;
   try {
@@ -84,12 +88,12 @@ async function executeWriteTransaction(address, abi) {
 
       logger.debug(JSON.stringify(receipt, null, 2));
 
-      printEventsInReceipt(receipt);
+      _printEventsInReceipt(receipt);
     }
   }
 }
 
-function printEventsInReceipt(receipt) {
+function _printEventsInReceipt(receipt) {
   const numEvents = receipt.events.length;
 
   if (numEvents > 0) {
@@ -97,7 +101,7 @@ function printEventsInReceipt(receipt) {
 
     receipt.events.map((event) => {
       if (event.event) {
-        logger.log(chalk.gray(`* ${event.event}(${event.args.join(', ')})`));
+        console.log(chalk.green(`✓ ${getFullEventSignature(hre.cli.contractName, event)}`));
       } else {
         logger.log(
           chalk.gray(`* Unknown event with topics: [${event.topics}] and data: [${event.data}]`)
diff --git a/packages/cli/subtasks/pick-function.js b/packages/cli/subtasks/pick-function.js
index 7175121e..cabcc3c1 100644
--- a/packages/cli/subtasks/pick-function.js
+++ b/packages/cli/subtasks/pick-function.js
@@ -1,16 +1,24 @@
 const { subtask } = require('hardhat/config');
 const { SUBTASK_PICK_FUNCTION } = require('../task-names');
 const prompts = require('prompts');
-const { getSignatureWithParameterNamesAndValues } = require('../internal/signatures');
+const chalk = require('chalk');
+const { getFullFunctionSignature } = require('../internal/signatures');
+const { getSelectors } = require('@synthetixio/core-js/utils/contracts');
 
 subtask(SUBTASK_PICK_FUNCTION, 'Pick a function from the given contract').setAction(
   async (taskArguments, hre) => {
     const abi = hre.deployer.deployment.abis[hre.cli.contractName];
     const abiFunctions = abi.filter((abiItem) => abiItem.name && abiItem.type === 'function');
+    const selectors = await getSelectors(abi);
 
     const choices = abiFunctions.map((abiItem) => {
+      const fullSignature = getFullFunctionSignature(hre.cli.contractName, abiItem.name);
+      const selector = selectors.find((selector) => selector.name === abiItem.name).selector;
+
       return {
-        title: getSignatureWithParameterNamesAndValues(hre.cli.contractName, abiItem.name),
+        title: `${chalk.gray(`${hre.cli.contractName}.`)}${fullSignature}${chalk.gray(
+          ` ${selector}`
+        )}`,
         value: abiItem.name,
       };
     });
diff --git a/packages/cli/subtasks/pick-parameters.js b/packages/cli/subtasks/pick-parameters.js
index bb7d0b8b..05dadf36 100644
--- a/packages/cli/subtasks/pick-parameters.js
+++ b/packages/cli/subtasks/pick-parameters.js
@@ -25,7 +25,7 @@ subtask(SUBTASK_PICK_PARAMETERS, 'Populate the selected function parameters').se
 
       if (answer) {
         try {
-          hre.cli.functionParameters.push(_parseInput(answer, parameter.type, hre));
+          hre.cli.functionParameters.push(await _parseInput(answer, parameter.type, hre));
 
           parameterIndex++;
         } catch (error) {
@@ -34,14 +34,15 @@ subtask(SUBTASK_PICK_PARAMETERS, 'Populate the selected function parameters').se
       } else {
         // Cancelling returns to pick-function
         hre.cli.functionName = null;
+        hre.cli.functionParameters = null;
         parameterIndex = functionAbi.inputs.length;
       }
     }
   }
 );
 
-function _parseInput(input, type, hre) {
-  const processed = _preprocessInput(input, type, hre);
+async function _parseInput(input, type, hre) {
+  const processed = await _preprocessInput(input, type, hre);
   if (input !== processed) {
     logger.info(`"${input}" auto-converted to "${processed}"`);
 
@@ -57,7 +58,7 @@ function _parseInput(input, type, hre) {
   return input;
 }
 
-function _preprocessInput(input, type, hre) {
+async function _preprocessInput(input, type, hre) {
   const isNumber = !isNaN(input);
   const isHex = hre.ethers.utils.isHexString(input);
 
@@ -66,5 +67,10 @@ function _preprocessInput(input, type, hre) {
     return hre.ethers.utils.formatBytes32String(input);
   }
 
+  // E.g. "self" or "signer" to signer address
+  if ((type === 'address' && input === 'self') || input === 'signer') {
+    return (await hre.ethers.getSigners())[0].address;
+  }
+
   return input;
 }
diff --git a/packages/cli/tasks/interact.js b/packages/cli/tasks/interact.js
index fcbd4310..77051f62 100755
--- a/packages/cli/tasks/interact.js
+++ b/packages/cli/tasks/interact.js
@@ -31,6 +31,8 @@ task(TASK_INTERACT, 'Interacts with a given modular system deployment')
     help += '* Use arrows to navigate, or type to autocomplete\n';
     help += '* Press enter to select a choice\n';
     help += '* Press ctrl-c to go back and exit\n';
+    help += '* For bytes32 types, strings will be utf converted\n';
+    help += '* For address types, self/signer will use signer address\n';
     logger.info(help, '\n');
 
     async function run() {
diff --git a/packages/core-contracts/hardhat.config.js b/packages/core-contracts/hardhat.config.js
index ebd724b4..b5cbbef7 100644
--- a/packages/core-contracts/hardhat.config.js
+++ b/packages/core-contracts/hardhat.config.js
@@ -1,6 +1,18 @@
+require('hardhat-contract-sizer');
 require('solidity-coverage');
 require('@nomiclabs/hardhat-ethers');
 
 module.exports = {
-  solidity: '0.8.4',
+  solidity: {
+    version: '0.8.7',
+    settings: {
+      optimizer: {
+        enabled: true,
+        runs: 200,
+      },
+    },
+  },
+  contractSizer: {
+    strict: true,
+  },
 };
diff --git a/packages/core-contracts/package.json b/packages/core-contracts/package.json
index 7426eaa5..74dcd9e5 100644
--- a/packages/core-contracts/package.json
+++ b/packages/core-contracts/package.json
@@ -8,7 +8,9 @@
   ],
   "scripts": {
     "test": "hardhat test",
-    "coverage": "hardhat coverage"
+    "coverage": "hardhat coverage",
+    "compile-contracts": "hardhat compile",
+    "size-contracts": "hardhat size-contracts"
   },
   "keywords": [],
   "author": "",
@@ -16,6 +18,7 @@
   "devDependencies": {
     "@nomiclabs/hardhat-ethers": "2.0.2",
     "hardhat": "2.6.8",
+    "hardhat-contract-sizer": "2.1.1",
     "solidity-coverage": "0.7.17"
   }
 }
diff --git a/packages/core-js/utils/logger.js b/packages/core-js/utils/logger.js
index bfd5e95d..09c04cbb 100644
--- a/packages/core-js/utils/logger.js
+++ b/packages/core-js/utils/logger.js
@@ -62,7 +62,7 @@ module.exports = {
   },
 
   notice(msg) {
-    this.log(chalk.yellow(`> ${msg}`));
+    this.log(chalk.yellow(`! ${msg}`));
   },
 
   error(msg) {
diff --git a/packages/core-modules/hardhat.config.js b/packages/core-modules/hardhat.config.js
index 07a38b91..64f77ab6 100644
--- a/packages/core-modules/hardhat.config.js
+++ b/packages/core-modules/hardhat.config.js
@@ -1,3 +1,4 @@
+require('hardhat-contract-sizer');
 require('solidity-coverage');
 require('@nomiclabs/hardhat-ethers');
 require('@synthetixio/deployer');
@@ -18,4 +19,7 @@ module.exports = {
       url: 'http://localhost:8545',
     },
   },
+  contractSizer: {
+    strict: true,
+  },
 };
diff --git a/packages/core-modules/package.json b/packages/core-modules/package.json
index 3f4dc0e5..adb3f2a6 100644
--- a/packages/core-modules/package.json
+++ b/packages/core-modules/package.json
@@ -11,7 +11,9 @@
   ],
   "scripts": {
     "test": "hardhat test",
-    "coverage": "hardhat coverage"
+    "coverage": "hardhat coverage",
+    "compile-contracts": "hardhat compile",
+    "size-contracts": "hardhat size-contracts"
   },
   "keywords": [],
   "author": "",
@@ -19,6 +21,7 @@
   "devDependencies": {
     "@nomiclabs/hardhat-ethers": "2.0.2",
     "hardhat": "2.6.8",
+    "hardhat-contract-sizer": "2.1.1",
     "solidity-coverage": "0.7.17"
   }
 }
diff --git a/packages/deployer/subtasks/deploy-contracts.js b/packages/deployer/subtasks/deploy-contracts.js
index 65c1e2a9..823377d2 100644
--- a/packages/deployer/subtasks/deploy-contracts.js
+++ b/packages/deployer/subtasks/deploy-contracts.js
@@ -34,19 +34,19 @@ subtask(
 async function _confirmDeployments({ toSkip, toUpdate, toCreate }) {
   if (toSkip.length > 0) {
     logger.info(`There are ${toSkip.length} contracts that are already up-to-date:`);
-    toSkip.forEach((contractName) => logger.notice(`  ${contractName}`));
+    toSkip.forEach((contractName) => logger.notice(contractName));
   }
 
   if (toUpdate.length > 0) {
     logger.info(`The following ${toUpdate.length} contracts are going to be updated:`);
-    toUpdate.forEach((contractName) => logger.notice(`  ${contractName}`));
+    toUpdate.forEach((contractName) => logger.notice(contractName));
   }
 
   if (toCreate.length > 0) {
     logger.info(
       `The following ${toCreate.length} contracts are going to be deployed for the first time:`
     );
-    toCreate.forEach((contractName) => logger.notice(`  ${contractName}`));
+    toCreate.forEach((contractName) => logger.notice(contractName));
   }
 
   return await prompter.ask('Are you sure you want to make these changes?');
diff --git a/packages/deployer/subtasks/print-info.js b/packages/deployer/subtasks/print-info.js
index 45dd5241..d63d19f0 100644
--- a/packages/deployer/subtasks/print-info.js
+++ b/packages/deployer/subtasks/print-info.js
@@ -5,11 +5,9 @@ const logger = require('@synthetixio/core-js/utils/logger');
 const prompter = require('@synthetixio/core-js/utils/prompter');
 const relativePath = require('@synthetixio/core-js/utils/relative-path');
 const { getCommit, getBranch } = require('@synthetixio/core-js/utils/git');
-const { readPackageJson } = require('@synthetixio/core-js/utils/npm');
 const { SUBTASK_PRINT_INFO } = require('../task-names');
 
 subtask(SUBTASK_PRINT_INFO, 'Prints info about a deployment.').setAction(async (taskArguments) => {
-  await logger.title(`${readPackageJson().name}\nDEPLOYER`);
   await _printInfo(taskArguments);
 
   await prompter.confirmAction('Proceed with deployment');
diff --git a/packages/deployer/subtasks/sync-sources.js b/packages/deployer/subtasks/sync-sources.js
index 3c949652..387ff28d 100644
--- a/packages/deployer/subtasks/sync-sources.js
+++ b/packages/deployer/subtasks/sync-sources.js
@@ -43,7 +43,7 @@ async function _removeDeletedSources({ sources, previousDeployment }) {
     logger.notice(
       'The following modules are not present in sources anymore, they will not be included on the deployment:'
     );
-    toRemove.forEach((source) => logger.notice(`  ${source}`));
+    toRemove.forEach((source) => logger.notice(source));
     await prompter.confirmAction('Do you confirm removing these modules?');
   }
 
@@ -66,7 +66,7 @@ async function _addNewSources({ sources, previousDeployment }) {
 
   if (toAdd.length > 0) {
     logger.notice('The following modules are going to be deployed for the first time:');
-    toAdd.forEach((source) => logger.notice(`  ${source}`));
+    toAdd.forEach((source) => logger.notice(source));
   }
 
   return toAdd.length > 0;
diff --git a/packages/deployer/tasks/deploy.js b/packages/deployer/tasks/deploy.js
index 0a56fa7a..704c9549 100644
--- a/packages/deployer/tasks/deploy.js
+++ b/packages/deployer/tasks/deploy.js
@@ -25,6 +25,7 @@ const logger = require('@synthetixio/core-js/utils/logger');
 const prompter = require('@synthetixio/core-js/utils/prompter');
 const types = require('@synthetixio/core-js/utils/hardhat/argument-types');
 const { ContractValidationError } = require('../internal/errors');
+const { readPackageJson } = require('@synthetixio/core-js/utils/npm');
 
 task(TASK_DEPLOY, 'Deploys all system modules')
   .addFlag('noConfirm', 'Skip all confirmation prompts', false)
@@ -45,6 +46,8 @@ task(TASK_DEPLOY, 'Deploys all system modules')
     logger.debugging = debug;
     prompter.noConfirm = noConfirm;
 
+    await logger.title(`${readPackageJson().name}\nDEPLOYER`);
+
     try {
       if (clear) {
         await hre.run(SUBTASK_CLEAR_DEPLOYMENTS, taskArguments);
diff --git a/packages/synthetix-main/hardhat.config.js b/packages/synthetix-main/hardhat.config.js
index dd3b6c5b..ea732396 100644
--- a/packages/synthetix-main/hardhat.config.js
+++ b/packages/synthetix-main/hardhat.config.js
@@ -1,5 +1,6 @@
 require('dotenv').config();
 
+require('hardhat-contract-sizer');
 require('solidity-coverage');
 require('@nomiclabs/hardhat-ethers');
 require('@synthetixio/deployer');
@@ -27,4 +28,7 @@ module.exports = {
   deployer: {
     proxyContract: 'Synthetix',
   },
+  contractSizer: {
+    strict: true,
+  },
 };
diff --git a/packages/synthetix-main/package.json b/packages/synthetix-main/package.json
index 9d512226..2a945811 100644
--- a/packages/synthetix-main/package.json
+++ b/packages/synthetix-main/package.json
@@ -8,7 +8,9 @@
   ],
   "scripts": {
     "test": "hardhat test",
-    "coverage": "hardhat coverage"
+    "coverage": "hardhat coverage",
+    "compile-contracts": "hardhat compile",
+    "size-contracts": "hardhat size-contracts"
   },
   "keywords": [],
   "author": "",
@@ -16,6 +18,7 @@
   "devDependencies": {
     "@nomiclabs/hardhat-ethers": "2.0.2",
     "hardhat": "2.6.8",
+    "hardhat-contract-sizer": "2.1.1",
     "solidity-coverage": "0.7.17"
   }
 }


Test patch:
diff --git a/packages/core-js/test/utils/logger.test.js b/packages/core-js/test/utils/logger.test.js
index d3a52f4c..b3943a8d 100644
--- a/packages/core-js/test/utils/logger.test.js
+++ b/packages/core-js/test/utils/logger.test.js
@@ -50,7 +50,7 @@ describe('utils/logger.js', () => {
       deepEqual(logged, [
         ['hello'],
         ['ⓘ  hello'],
-        ['> hello'],
+        ['! hello'],
         ['☠ hello'],
         ['⚠ hello'],
         ['✓ hello'],