Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BasicWhitelisting contract #302

Merged
merged 2 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions contracts/whitelisting/BasicWhitelisting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {ISSVWhitelistingContract} from "../interfaces/external/ISSVWhitelistingContract.sol";

contract BasicWhitelisting is ISSVWhitelistingContract, ERC165, Ownable {
mapping(address => bool) private whitelisted;

event AddressWhitelisted(address indexed account);
event AddressRemovedFromWhitelist(address indexed account);

function addWhitelistedAddress(address account) external onlyOwner {
whitelisted[account] = true;
emit AddressWhitelisted(account);
}

function removeWhitelistedAddress(address account) external onlyOwner {
whitelisted[account] = false;
emit AddressRemovedFromWhitelist(account);
}

function isWhitelisted(address account, uint256) external view override returns (bool) {
return whitelisted[account];
}

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(ISSVWhitelistingContract).interfaceId || super.supportsInterface(interfaceId);
}
}
17 changes: 17 additions & 0 deletions tasks/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ task('deploy:main-impl', 'Deploys SSVNetwork / SSVNetworkViews implementation co
await hre.run('deploy:impl', { contract });
});

/**
@title Hardhat task to deploy a basic whitelisting contract implementation.
The deployment process involves running a subtask that handles the actual deployment.
@returns {void} This function doesn't return anything. If the deployment process encounters an error,
it will be printed to the console, and the process will exit with a non-zero status code.
@example
// Deploy BasicWhitelisting contract with the default deployer account
npx hardhat --network holesky_testnet deploy:whitelisting-contract
@remarks
The deployer account used will be the first one returned by ethers.getSigners().
Therefore, it should be appropriately configured in your Hardhat network configuration.
This task uses the "deploy:impl" subtask for the actual deployment, specifying 'BasicWhitelisting' as the contract name.
*/
task('deploy:whitelisting-contract', 'Deploys a basic whitelisting contract').setAction(async (_, hre) => {
await hre.run('deploy:impl', { contract: 'BasicWhitelisting' });
});

/**
@title Hardhat subtask to deploy an SSV module contract.
The module parameter specifies the name of the SSV module to be deployed.
Expand Down
86 changes: 86 additions & 0 deletions test/operators/external-whitelist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import hre from 'hardhat';

import { assertEvent } from '../helpers/utils/test';
const { expect } = require('chai');

describe('BasicWhitelisting', () => {
let basicWhitelisting: any, owners: any;

beforeEach(async () => {
owners = await hre.viem.getWalletClients();

basicWhitelisting = await hre.viem.deployContract('BasicWhitelisting');
});

describe('Deployment', async () => {
it('Should set the right owner', async () => {
expect(await basicWhitelisting.read.owner()).to.deep.equal(owners[0].account.address);
});
});

describe('Whitelisting', async () => {
it('Should whitelist an address', async () => {
const addr1 = owners[2].account.address;

await basicWhitelisting.write.addWhitelistedAddress([addr1]);
expect(await basicWhitelisting.read.isWhitelisted([addr1, 0])).to.be.true;
});

it('Should remove an address from whitelist', async () => {
const addr1 = owners[2].account.address;

await basicWhitelisting.write.addWhitelistedAddress([addr1]);
await basicWhitelisting.write.removeWhitelistedAddress([addr1]);
expect(await basicWhitelisting.read.isWhitelisted([addr1, 0])).to.be.false;
});

it('Should emit AddressWhitelisted event', async () => {
const addr1 = owners[2].account.address;

await assertEvent(basicWhitelisting.write.addWhitelistedAddress([addr1]), [
{
contract: basicWhitelisting,
eventName: 'AddressWhitelisted',
argNames: ['account'],
argValuesList: [[addr1]],
},
]);
});

it('Should emit AddressRemovedFromWhitelist event', async () => {
const addr1 = owners[2].account.address;

await basicWhitelisting.write.addWhitelistedAddress([addr1]);

await assertEvent(basicWhitelisting.write.removeWhitelistedAddress([addr1]), [
{
contract: basicWhitelisting,
eventName: 'AddressRemovedFromWhitelist',
argNames: ['account'],
argValuesList: [[addr1]],
},
]);
});

it('Should only allow the owner to whitelist addresses', async () => {
const addr1 = owners[2].account.address;

await expect(
basicWhitelisting.write.addWhitelistedAddress([addr1], {
account: owners[1].account,
}),
).to.be.rejectedWith('Ownable: caller is not the owner');
});

it('Should only allow the owner to remove addresses from whitelist', async () => {
const addr1 = owners[2].account.address;

await basicWhitelisting.write.addWhitelistedAddress([addr1]);
await expect(
basicWhitelisting.write.removeWhitelistedAddress([addr1], {
account: owners[1].account,
}),
).to.be.rejectedWith('Ownable: caller is not the owner');
});
});
});
Loading