Upgradeable Solidity contract with access management.
- Manager.sol is an upgradeable Solidity contract built with OpenZeppelin's upgradeable libraries that offers a secure and flexible solution for managing signature verifications. It combines a robust access control system, pausing functionality for emergencies, and upgradeability with state migration to ensure the contract remains secure and maintainable over time.
- Verifier.sol is a simple Solidity contract that allows to register and revoke signatures.
- Deploy.sol is a script that deploys the Manager.sol and Verifier.sol contracts.
- test/Manager.t.sol is a test file that tests the Manager.sol contract.
- test/Verifier.t.sol is a test file that tests the Verifier.sol contract.
- The
Initializable
base contract is use to guarantee that a contract is only initialized once. - The
Upgradeable
base contract is use to guarantee that a contract is upgradeable. - The
AccessControl
base contract is use to manage access control. - The
Pausable
base contract is use to pause the contract in case of emergency.
The Manager contract implements a role-based access control system to restrict functionalities to authorized users. There are three primary roles:
- Admin (DEFAULT_ADMIN_ROLE): Has full control over the contract, can execute all methods, and is the only role allowed to remove roles.
- Maintainer (MAINTAINER_ROLE): Authorized to upgrade the contract and assign other maintainers.
- Authority (AUTHORITY_ROLE): Empowered to set or update the verifier contract and assign other authorities.
The contract supports upgradeability via an upgrade function that can only be called by an Admin or a Maintainer. During an upgrade, the current state—specifically the verifier address and the verification counter—is migrated to a new contract instance through a defined migration interface. After upgrading, no further operations can be performed on the original contract, ensuring a secure transition.
Manager incorporates a pausability feature that allows an Admin to pause or unpause the contract in emergency scenarios. When paused, all functions—including verification, access control modifications, and upgrades—are disabled, safeguarding the contract from potential malicious activities or unforeseen issues until the situation is resolved.
The contract performs batch verification of signatures by interacting with an external verifier contract. The batchVerify
function processes an array of signatures for a given user, calling the verifier's verify
method on each signature. Each successful verification increments a counter using SafeMath to prevent overflows, enabling accurate tracking of valid verifications.
Robust error handling is built into the contract to ensure that if any call fails—such as an invalid verifier address or an error during verification—the entire transaction is reverted. This mechanism prevents partial state updates and ensures the integrity and consistency of the contract's state under all conditions.
Start Anvil:
curl -L https://foundry.paradigm.xyz | bash
foundryup
anvil
Install Foundry dependencies:
forge install foundry-rs/forge-std
Install OpenZeppelin dependencies:
forge install OpenZeppelin/openzeppelin-contracts
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
To test the Manager contract, run the following command:
forge test
Response:
[...]
Ran 2 test suites in 298.44ms (2.46ms CPU time): 21 tests passed, 0 failed, 0 skipped (21 total tests)
Next, to deploy the Manager and Verifier contracts, run the following command:
forge script ./Deploy.sol \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast
Response:
[...]
Script ran successfully.
== Logs ==
Manager proxy deployed at: 0xc6e7DF5E7b4f2A278906862b61205850344D4e7d
Verifier proxy deployed at: 0x59b670e9fA9D0A427751Af201D676719a970857b
[...]
Export the contract ABIs:
cat out/Manager.sol/Manager.json | jq -r '.abi' > Manager.abi
cat out/Verifier.sol/Verifier.json | jq -r '.abi' > Verifier.abi
Set the env vars before running the tests:
# Update these with your proxy addresses from deployment
export MANAGER_ADDRESS=0xc6e7DF5E7b4f2A278906862b61205850344D4e7d
export VERIFIER_ADDRESS=0x59b670e9fA9D0A427751Af201D676719a970857b
export ADMIN_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
export ADMIN_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export ETHEREUM_RPC_URL=http://127.0.0.1:8545
export AUTHORITY_ROLE=$(cast keccak "AUTHORITY_ROLE")
export USER_ADDRESS=0x742d35cc6634c0532925a3b844f51317abc046cd
export USER_SIGNATURE=0x0000000000000000000000000000000000000000000000000000000000000000
Grant AUTHORITY_ROLE to admin (needed to set verifier):
cast send $MANAGER_ADDRESS "assignRole(bytes32,address)" $AUTHORITY_ROLE $ADMIN_ADDRESS \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Pause the Manager contract:
cast send $MANAGER_ADDRESS "pause()" \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Verify the contract is paused:
cast call $MANAGER_ADDRESS "paused()" --rpc-url $ETHEREUM_RPC_URL
Unpause the Manager contract:
cast send $MANAGER_ADDRESS "unpause()" \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Set the Verifier contract in the Manager:
cast send $MANAGER_ADDRESS "setVerifier(address)" $VERIFIER_ADDRESS \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Verify the verifier address is set correctly:
cast call $MANAGER_ADDRESS "getVerifier()" --rpc-url $ETHEREUM_RPC_URL
Register a signature in the Verifier contract:
cast send $VERIFIER_ADDRESS "registerSignature(address,bytes32)" $USER_ADDRESS $USER_SIGNATURE \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Verify the signature through the Manager contract:
# Create an array with one signature for batch verification
export SIGNATURES="[$USER_SIGNATURE]"
cast send $MANAGER_ADDRESS "batchVerify(address,bytes32[])" $USER_ADDRESS $SIGNATURES \
--private-key $ADMIN_PRIVATE_KEY --rpc-url $ETHEREUM_RPC_URL
Check the verification counter (should be 1):
cast call $MANAGER_ADDRESS "getVerificationCounter()" --rpc-url $ETHEREUM_RPC_URL
The counter value will be returned in hex format. To convert it to decimal:
cast --to-dec $(cast call $MANAGER_ADDRESS "getVerificationCounter()" --rpc-url $ETHEREUM_RPC_URL)