The Consul smart contract is designed to manage a decentralized network of Praetor nodes and their corresponding servers. The contract enables the owner and controllers to execute commands, manage Praetors, and maintain a command history. It also provides an optional "dictator mode" as a fallback mechanism in case the Praetor nodes are compromised. By leveraging blockchain technology, this architecture provides censorship resistance and increased security against attacks, making it difficult for bad actors to take control of the network.
Praetors are represented as structs containing details such as the server's IP address, port, ENS name, node IP address, and port. The contract provides functions to:
- Add a new Praetor
- Remove a Praetor
- Deactivate a Praetor
The contract maintains a history of commands executed in the network. The current command is set to "REPORT" by default. Controllers can change the current command, and the previous command is added to the command history. The contract provides functions to:
- Change the current command
- Get the current command
- Get a command from command history by index
- Get the length of the command history
Dictator mode is a fallback mechanism that, when enabled, restricts the network to execute commands only through the Consul contract. This mode is useful in cases where the Praetor nodes are compromised, providing the owner with the ability to maintain control until the nodes are restored. Dictator mode comes with some trade-offs, such as increased gas costs and potentially reduced features.
In dictator mode, payloads can be added and removed by controllers and the owner, respectively. The payloads can be a list of email addresses for spamming or IP addresses for DDoS attacks, among other things.
The contract provides functions to:
- Toggle dictator mode
- Get the dictator mode state
- Add a payload
- Remove a payload
- Get a payload by its ID
The contract uses OpenZeppelin's AccessControl for managing roles and permissions. There are two main roles:
- Owner (OWNER_ROLE): Has full control over the contract and can delegate control to a third party.
- Controller (CONTROLLER_ROLE): Can manage commands and payloads but cannot change the owner or manage Praetors.
The owner can transfer ownership to a new address and grant or revoke the controller role.
By utilizing blockchain technology, the Consul smart contract architecture provides censorship resistance and increased security against attacks. The decentralized nature of the network makes it harder for bad actors to take control or manipulate the system. Moreover, the immutability of the blockchain ensures that the command history and Praetor management actions cannot be tampered with, providing a transparent and auditable record of the network's operations.
Statements | Functions | Lines |
---|---|---|
- Docker
PATH+=":./bin" # use your sh files (which are located in bin/) directly from the root of the project
yarn install # install deps
yarn run build # install solc and other tools in the docker image
Don't forget to copy the .env.example file to a file named .env, and then edit it to fill in the details.
yarn run test
yarn run test:trace # shows logs + calls
yarn run test:fresh # force compile and then run tests
yarn run test:coverage # run tests with coverage reports
You can use the below packages,
- Solhint
- ESLint
- Prettier
- CSpell
- ShellCheck
yarn run format
yarn run lint
You can use the below tools,
- Slither
- Mythril
yarn run analyze:static path/to/contract
yarn run analyze:security path/to/contract
yarn run analyze:all path/to/contract
To try out Etherscan verification, you first need to deploy a contract to an Ethereum network that's supported by Etherscan, such as Ropsten.
In this project, copy the .env.example file to a file named .env, and then edit it to fill in the details.
- Enter your Etherscan API key
- Ropsten node URL (eg from Alchemy)
- The private key of the account which will send the deployment transaction.
With a valid .env file in place, first deploy your contract:
yarn run deploy ropsten <CONTRACT_FILE_NAME> # related to scripts/deploy/<CONTRACT_FILE_NAME>.ts
yarn run deploy:all ropsten # related to scripts/deploy.ts
Also, you can add contract(s) manually to your tenderly projects from the output.
https://dashboard.tenderly.co/contract/<NETWORK_NAME>/<CONTRACT_ADDRESS>
And then verify it:
yarn run verify ropsten <DEPLOYED_CONTRACT_ADDRESS> "<CONSTRUCTOR_ARGUMENT(S)>" # hardhat.config.ts to see all networks
yarn run finder --path contracts/Workshop.sol --name Workshop abi --colorify --compact --prettify # find contract outputs of specific contract
yarn run finder --help # see all supported outputs (abi, metadata, bytecode and more than 20+ outputs)
yarn run generate:docs # generate docs according to the contracts/ folder
yarn run generate:flatten ./path/to/contract # generate the flatten file (path must be "./" prefixed)
yarn run generate:abi ./path/to/contract # generate the ABI file (path must be "./" prefixed)
yarn run generate:bin ./path/to/contract # generate the binary in a hex (path must be "./" prefixed)
yarn run generate:metadata ./path/to/contract # generate the metadata (path must be "./" prefixed)
yarn run generate:all-abi
yarn run generate:all-bin
yarn run generate:all-metadata
yarn run share # share project folder with remix ide
First, create a "Hello World" Python script file named hello_world.py:
Then, use the following Python code to convert the hello_world.py file into a hexadecimal string:
with open("hello_world.py", "rb") as file:
content = file.read()
hex_string = content.hex()
print(hex_string)
Run the Python script above, and it will output a hexadecimal string representing the contents of the hello_world.py file. Copy this hexadecimal string and use it as input for TypeScript.
Replace the retrieved_payload variable with the actual payload data returned from the getPayload function:
def bytes_array_to_hex(byte_array):
return ''.join(byte[2:] for byte in byte_array)
def hex_to_str(hex_string):
return bytes.fromhex(hex_string).decode('utf-8')
retrieved_payload = ['0x70', '0x72', '0x69', '0x6e', '0x74', '0x28', '0x48', '0x65', '0x6c', '0x6c', '0x6f', '0x2c', '0x20', '0x57', '0x6f', '0x72', '0x6c', '0x64', '0x21', '0x29'] # Replace this with the output from getPayload
hex_string = bytes_array_to_hex(retrieved_payload)
decoded_string = hex_to_str(hex_string)
print(decoded_string)
Credit to https://github.com/emretepedev/solidity-hardhat-typescript-boilerplate for the template