mirror of
https://github.com/Instadapp/dsa-connectors.git
synced 2024-07-29 22:37:00 +00:00
Merge pull request #35 from Instadapp/mapping-contract
Mapping contract
This commit is contained in:
commit
92bb147509
172
contracts/mapping/InstaMappingController.sol
Normal file
172
contracts/mapping/InstaMappingController.sol
Normal file
|
@ -0,0 +1,172 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.6.0 <0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
import "@openzeppelin/contracts/utils/Context.sol";
|
||||
|
||||
interface IndexInterface {
|
||||
function master() external view returns (address);
|
||||
}
|
||||
|
||||
contract InstaMappingController is Context {
|
||||
using EnumerableSet for EnumerableSet.AddressSet;
|
||||
using Address for address;
|
||||
|
||||
mapping(address => EnumerableSet.AddressSet) private _roles;
|
||||
|
||||
IndexInterface public constant instaIndex =
|
||||
IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723);
|
||||
|
||||
/**
|
||||
* @dev Emitted when `account` is granted `role`.
|
||||
*/
|
||||
event RoleGranted(address indexed role, address indexed account);
|
||||
|
||||
/**
|
||||
* @dev Emitted when `account` is revoked `role`.
|
||||
*
|
||||
* `sender` is the account that originated the contract call:
|
||||
* - if using `revokeRole`, it is the insta master
|
||||
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
|
||||
*/
|
||||
event RoleRevoked(
|
||||
address indexed role,
|
||||
address indexed account,
|
||||
address indexed sender
|
||||
);
|
||||
|
||||
modifier onlyMaster {
|
||||
require(
|
||||
instaIndex.master() == _msgSender(),
|
||||
"MappingController: sender must be master"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns `true` if `account` has been granted `role`.
|
||||
*/
|
||||
function hasRole(address role, address account) public view returns (bool) {
|
||||
return _roles[role].contains(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the number of accounts that have `role`. Can be used
|
||||
* together with {getRoleMember} to enumerate all bearers of a role.
|
||||
*/
|
||||
function getRoleMemberCount(address role) public view returns (uint256) {
|
||||
return _roles[role].length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns one of the accounts that have `role`. `index` must be a
|
||||
* value between 0 and {getRoleMemberCount}, non-inclusive.
|
||||
*
|
||||
* Role bearers are not sorted in any particular way, and their ordering may
|
||||
* change at any point.
|
||||
*
|
||||
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
|
||||
* you perform all queries on the same block. See the following
|
||||
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
|
||||
* for more information.
|
||||
*/
|
||||
function getRoleMember(address role, uint256 index)
|
||||
public
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return _roles[role].at(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Grants `role` to `account`.
|
||||
*
|
||||
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||||
* event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must be the master.
|
||||
*/
|
||||
function grantRole(address role, address account)
|
||||
public
|
||||
virtual
|
||||
onlyMaster
|
||||
{
|
||||
_grantRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes `role` from `account`.
|
||||
*
|
||||
* If `account` had been granted `role`, emits a {RoleRevoked} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must be the master.
|
||||
*/
|
||||
function revokeRole(address role, address account)
|
||||
public
|
||||
virtual
|
||||
onlyMaster
|
||||
{
|
||||
_revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes `role` from the calling account.
|
||||
*
|
||||
* Roles are often managed via {grantRole} and {revokeRole}: this function's
|
||||
* purpose is to provide a mechanism for accounts to lose their privileges
|
||||
* if they are compromised (such as when a trusted device is misplaced).
|
||||
*
|
||||
* If the calling account had been granted `role`, emits a {RoleRevoked}
|
||||
* event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must be `account`.
|
||||
*/
|
||||
function renounceRole(address role, address account) public virtual {
|
||||
require(
|
||||
account == _msgSender(),
|
||||
"MappingController: can only renounce roles for self"
|
||||
);
|
||||
|
||||
_revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Grants `role` to `account`.
|
||||
*
|
||||
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||||
* event. Note that unlike {grantRole}, this function doesn't perform any
|
||||
* checks on the calling account.
|
||||
*
|
||||
* [WARNING]
|
||||
* ====
|
||||
* This function should only be called from the constructor when setting
|
||||
* up the initial roles for the system.
|
||||
*
|
||||
* Using this function in any other way is effectively circumventing the admin
|
||||
* system imposed by {AccessControl}.
|
||||
* ====
|
||||
*/
|
||||
function _setupRole(address role, address account) internal virtual {
|
||||
_grantRole(role, account);
|
||||
}
|
||||
|
||||
function _grantRole(address role, address account) private {
|
||||
if (_roles[role].add(account)) {
|
||||
emit RoleGranted(role, account);
|
||||
}
|
||||
}
|
||||
|
||||
function _revokeRole(address role, address account) private {
|
||||
if (_roles[role].remove(account)) {
|
||||
emit RoleRevoked(role, account, _msgSender());
|
||||
}
|
||||
}
|
||||
}
|
27439
package-lock.json
generated
27439
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -39,6 +39,8 @@
|
|||
"@openzeppelin/test-helpers": "^0.5.6",
|
||||
"@studydefi/money-legos": "^2.3.7",
|
||||
"@tenderly/hardhat-tenderly": "^1.0.6",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"ethereum-waffle": "^3.3.0",
|
||||
"ethers": "^5.0.32",
|
||||
"hardhat": "^2.0.8",
|
||||
"husky": "^6.0.0",
|
||||
|
|
164
test/01_insta_access_control.js
Normal file
164
test/01_insta_access_control.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
const { ethers, network } = require("hardhat");
|
||||
const chai = require("chai");
|
||||
const chaiPromise = require("chai-as-promised");
|
||||
const { solidity } = require("ethereum-waffle");
|
||||
|
||||
chai.use(chaiPromise);
|
||||
chai.use(solidity);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
const getMapping = (address, signer) => {
|
||||
return ethers.getContractAt("InstaMappingController", address, signer);
|
||||
};
|
||||
|
||||
describe("Test InstaMapping contract", () => {
|
||||
let account, instaMaster;
|
||||
let mappingAddress;
|
||||
let masterMapping;
|
||||
const indexInterfaceAddress = "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723";
|
||||
const testRoleAddress = "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723";
|
||||
|
||||
before("get signers", async () => {
|
||||
[account] = await ethers.getSigners();
|
||||
|
||||
const IndexContract = await ethers.getContractAt(
|
||||
"contracts/mapping/InstaMappingController.sol:IndexInterface",
|
||||
indexInterfaceAddress
|
||||
);
|
||||
const masterAddress = await IndexContract.master();
|
||||
|
||||
await network.provider.request({
|
||||
method: "hardhat_impersonateAccount",
|
||||
params: [masterAddress],
|
||||
});
|
||||
|
||||
instaMaster = await ethers.getSigner(masterAddress);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await network.provider.request({
|
||||
method: "hardhat_stopImpersonatingAccount",
|
||||
params: [instaMaster.address],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach("deploy contract", async () => {
|
||||
const mappingFactory = await ethers.getContractFactory(
|
||||
"InstaMappingController"
|
||||
);
|
||||
const mapping = await mappingFactory.deploy();
|
||||
|
||||
await mapping.deployed();
|
||||
mappingAddress = mapping.address;
|
||||
|
||||
masterMapping = await getMapping(mappingAddress, instaMaster);
|
||||
});
|
||||
|
||||
it("grant,revoke role should fail with non master signer", async () => {
|
||||
const selfMapping = await getMapping(mappingAddress, account);
|
||||
|
||||
await expect(
|
||||
selfMapping.grantRole(testRoleAddress, account.address)
|
||||
).to.rejectedWith(/MappingController: sender must be master/);
|
||||
|
||||
await expect(
|
||||
selfMapping.revokeRole(testRoleAddress, account.address)
|
||||
).to.rejectedWith(/MappingController: sender must be master/);
|
||||
});
|
||||
|
||||
it("hasRole should return false for roles not assigned to users", async () => {
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should grant roles", async () => {
|
||||
await expect(masterMapping.grantRole(testRoleAddress, account.address))
|
||||
.to.emit(masterMapping, "RoleGranted")
|
||||
.withArgs(testRoleAddress, account.address);
|
||||
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should revoke role", async () => {
|
||||
// add a role first
|
||||
await masterMapping.grantRole(testRoleAddress, account.address);
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
true
|
||||
);
|
||||
|
||||
// then remove the role
|
||||
await expect(masterMapping.revokeRole(testRoleAddress, account.address))
|
||||
.to.emit(masterMapping, "RoleRevoked")
|
||||
.withArgs(testRoleAddress, account.address, instaMaster.address);
|
||||
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should renounce role only with the account not master", async () => {
|
||||
// add a role first
|
||||
await masterMapping.grantRole(testRoleAddress, account.address);
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
true
|
||||
);
|
||||
|
||||
// then renounce the the role
|
||||
await expect(
|
||||
masterMapping.renounceRole(testRoleAddress, account.address)
|
||||
).to.rejectedWith(/MappingController: can only renounce roles for self/);
|
||||
|
||||
const selfMapping = await getMapping(mappingAddress, account);
|
||||
expect(await selfMapping.renounceRole(testRoleAddress, account.address))
|
||||
.to.emit(masterMapping, "RoleRevoked")
|
||||
.withArgs(testRoleAddress, account.address, account.address);
|
||||
|
||||
expect(await masterMapping.hasRole(testRoleAddress, account.address)).to.eq(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should do role count properly", async () => {
|
||||
expect(await masterMapping.getRoleMemberCount(testRoleAddress)).to.eq(0);
|
||||
|
||||
await masterMapping.grantRole(testRoleAddress, account.address);
|
||||
|
||||
expect(await masterMapping.getRoleMemberCount(testRoleAddress)).to.eq(1);
|
||||
|
||||
await masterMapping.grantRole(testRoleAddress, instaMaster.address);
|
||||
|
||||
expect(await masterMapping.getRoleMemberCount(testRoleAddress)).to.eq(2);
|
||||
|
||||
await masterMapping.revokeRole(testRoleAddress, instaMaster.address);
|
||||
|
||||
expect(await masterMapping.getRoleMemberCount(testRoleAddress)).to.eq(1);
|
||||
});
|
||||
|
||||
it("should get member correctly by index", async () => {
|
||||
await expect(
|
||||
masterMapping.getRoleMember(testRoleAddress, 0)
|
||||
).to.rejectedWith(/EnumerableSet: index out of bounds/);
|
||||
|
||||
await masterMapping.grantRole(testRoleAddress, account.address);
|
||||
|
||||
expect(await masterMapping.getRoleMember(testRoleAddress, 0)).to.eq(
|
||||
account.address
|
||||
);
|
||||
|
||||
await masterMapping.grantRole(testRoleAddress, instaMaster.address);
|
||||
|
||||
expect(await masterMapping.getRoleMember(testRoleAddress, 1)).to.eq(
|
||||
instaMaster.address
|
||||
);
|
||||
|
||||
await masterMapping.revokeRole(testRoleAddress, instaMaster.address);
|
||||
|
||||
await expect(
|
||||
masterMapping.getRoleMember(testRoleAddress, 1)
|
||||
).to.rejectedWith(/EnumerableSet: index out of bounds/);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user