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

feat: add oracle daemon config contract #558

Merged
merged 5 commits into from
Feb 6, 2023
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
80 changes: 80 additions & 0 deletions contracts/0.8.9/OracleDaemonConfig.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

/* See contracts/COMPILERS.md */
pragma solidity 0.8.9;

import {AccessControlEnumerable} from "./utils/access/AccessControlEnumerable.sol";

contract OracleDaemonConfig is AccessControlEnumerable {

bytes32 public constant CONFIG_MANAGER_ROLE = keccak256("CONFIG_MANAGER_ROLE");

mapping(string => bytes) private values;

constructor(address _admin, address[] memory _configManagers) {
if (_admin == address(0)) revert ErrorZeroAddress();

_grantRole(DEFAULT_ADMIN_ROLE, _admin);
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved

for (uint256 i = 0; i < _configManagers.length; ) {
if (_configManagers[i] == address(0)) revert ErrorZeroAddress();
_grantRole(CONFIG_MANAGER_ROLE, _configManagers[i]);

unchecked {
++i;
}
}
}

function set(string calldata _key, bytes calldata _value) external onlyRole(CONFIG_MANAGER_ROLE) {
if (values[_key].length > 0) revert ErrorValueExists(_key);
values[_key] = _value;

emit ConfigValueSet(_key, _value);
}

function update(string calldata _key, bytes calldata _value) external onlyRole(CONFIG_MANAGER_ROLE) {
if (values[_key].length == 0) revert ErrorValueDoesntExist(_key);
values[_key] = _value;

emit ConfigValueUpdated(_key, _value);
}

function unset(string calldata _key) external onlyRole(CONFIG_MANAGER_ROLE) {
if (values[_key].length == 0) revert ErrorValueDoesntExist(_key);
delete values[_key];

emit ConfigValueUnset(_key);
}

function get(string calldata _key) external view returns (bytes memory) {
if (values[_key].length == 0) revert ErrorValueDoesntExist(_key);

return values[_key];
}

function getList(string[] calldata _keys) external view returns (bytes[] memory) {
bytes[] memory results = new bytes[](_keys.length);

for (uint256 i = 0; i < _keys.length; ) {
if (values[_keys[i]].length == 0) revert ErrorValueDoesntExist(_keys[i]);

results[i] = values[_keys[i]];

unchecked {
++i;
}
}

return results;
}

error ErrorValueExists(string key);
error ErrorValueDoesntExist(string key);
error ErrorZeroAddress();

event ConfigValueSet(string key, bytes value);
event ConfigValueUpdated(string key, bytes value);
event ConfigValueUnset(string key);
}
1 change: 1 addition & 0 deletions lib/abi/OracleDaemonConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address[]","name":"_configManagers","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"ErrorValueDoesntExist","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"ErrorValueExists","type":"error"},{"inputs":[],"name":"ErrorZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"ConfigValueSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"ConfigValueUnset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"ConfigValueUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"CONFIG_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_key","type":"string"}],"name":"get","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"_keys","type":"string[]"}],"name":"getList","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_key","type":"string"},{"internalType":"bytes","name":"_value","type":"bytes"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_key","type":"string"}],"name":"unset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_key","type":"string"},{"internalType":"bytes","name":"_value","type":"bytes"}],"name":"update","outputs":[],"stateMutability":"nonpayable","type":"function"}]
128 changes: 128 additions & 0 deletions test/0.8.9/oracle-daemon-config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const hre = require('hardhat')
const { keccak256 } = require('js-sha3')
const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test')

const { assert } = require('../helpers/assert')
const { EvmSnapshot } = require('../helpers/blockchain')

const OracleDaemonConfig = hre.artifacts.require('OracleDaemonConfig.sol')

contract('OracleDaemonConfig', async ([deployer, manager, stranger]) => {
let config, snapshot
const defaultKey = '12345'
const defaultValue = '0x'.padEnd(66, '0101')
const updatedDefaultValue = '0x'.padEnd(66, '0202')

before(async () => {
config = await OracleDaemonConfig.new(deployer, [manager], { from: deployer })
snapshot = new EvmSnapshot(hre.ethers.provider)

await snapshot.make()
})

describe('happy path', async () => {
before(async () => {
await snapshot.rollback()
})

it('sets a value', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
})

it('gets a value', async () => {
const value = await config.get(defaultKey)

assert.equal(defaultValue, value)
})

it('updates a value', async () => {
await config.update(defaultKey, updatedDefaultValue, { from: manager })

const value = await config.get(defaultKey)

assert.notEqual(defaultValue, value)
assert.equal(updatedDefaultValue, value)
})

it('gets all values', async () => {
const values = await config.getList([defaultKey])

assert.equal(values.length, 1)
assert.deepEqual(
values,
[updatedDefaultValue]
)
})

it('removes a value', async () => {
await config.unset(defaultKey, { from: manager })

assert.reverts(config.get(defaultKey))
})

it('reverts while gets all values', async () => {
assert.reverts(config.getList([defaultKey]), `ErrorValueDoesntExist(${defaultKey})`)
})
})

describe('edge cases', async () => {
beforeEach(async () => {
await snapshot.rollback()
})

it("reverts when defaultValue for update doesn't exist", async () => {
assert.reverts(config.update(defaultKey, defaultValue, { from: manager }), `ErrorValueDoesntExist(${defaultKey})`)
})

it("reverts when defaultValue for unset doen't exist", async () => {
assert.reverts(config.unset(defaultKey, { from: manager }), `ErrorValueDoesntExist(${defaultKey})`)
})

it('reverts when defaultValue for set already exists', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
assert.reverts(config.set(defaultKey, updatedDefaultValue, { from: manager }), `ErrorValueExists(${defaultKey})`)
})

it('reverts when admin is zero address', async () => {
assert.reverts(OracleDaemonConfig.new(ZERO_ADDRESS, [manager], { from: deployer }), 'ErrorZeroAddress()')
})

it('reverts when one of managers is zero address', async () => {
assert.reverts(OracleDaemonConfig.new(deployer, [manager, ZERO_ADDRESS], { from: deployer }), 'ErrorZeroAddress()')
})
})

describe('access control', async () => {
beforeEach(async () => {
await snapshot.rollback()
})

it('stranger cannot set a defaultValue', async () => {
assert.reverts(config.set(defaultKey, defaultValue, { from: stranger }))
})

it('admin cannot set a defaultValue', async () => {
assert.reverts(config.set(defaultKey, defaultValue, { from: deployer }))
})

it('stranger cannot update a defaultValue', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
assert.reverts(config.update(defaultKey, updatedDefaultValue, { from: stranger }))
})

it('admin cannot update a defaultValue', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
assert.reverts(config.update(defaultKey, updatedDefaultValue, { from: deployer }))
})

it('stranger cannot unset a defaultValue', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
assert.reverts(config.unset(defaultKey, { from: stranger }))
})

it('stranger cannot unset a defaultValue', async () => {
await config.set(defaultKey, defaultValue, { from: manager })
assert.reverts(config.unset(defaultKey, { from: deployer }))
})
})
})