3

So here is my problem .

I have 2 users of my DApp : Admin and User

Admin is the contract owner and can access admin panel of my DApp . User is a normal user having something like a conditional access . The contract owner creates a transactionId ( similar to a token Id ) and corresponding to that Id I have mapped submitters and approvers . Submitter submits a transaction and approver approves that transaction after transaction has been submitted by the submitter .

Now my idea is that the User's UI will somewhat be like he gets 2 lists : one containing transactions that he has to submit ( the admin granted him the role of submitter for those particular transaction Ids ) and , the other list is containing transactions that he has to approve ( the admin granted him the role of approver for those particular transaction Ids ) .

I'll share the code snippet here :

    contract MyContract {
        constructor() {
            admin = msg.sender;
            _grantRole(ADMIN,admin); // when contract is deployed ADMIN role is granted to msg.sender i.e. the deployer of the contract 
        
        }
    mapping(uint256 => address[]) public transactionIdToApproversArray; 
    mapping(uint256 => address[]) public transactionIdToSubmittersArray; 

    function submit(uint256 _transactionId, address[] memory _submitters ,address[] memory _approvers,uint256 _numConfirmationsRequired) external onlyRole(ADMIN) {
        
        require(_approvers.length>1,"Approvers required must be greater than 1");
        require(_numConfirmationsRequired>0 && _numConfirmationsRequired<=_approvers.length,"Num of confirmations are not in sync with the number of approvers");
        for (uint i = 0; i < _submitters.length; i++) {
            require(_submitters[i] != address(0),"address cannot be a zero address");
            _grantRole(SUBMITTER,_submitters[i]);
// do something
        }

        for (uint i=0;i<_approvers.length;i++){
            require(_approvers[i] != address(0),"address cannot be a zero address");
            _grantRole(APPROVER,_approvers[i]);
// do something 
        }
    }
    }

The problem that I currently have is when I pass an array of submitters and approvers , they are set as universal submitters and approvers ( means they have become submitters and approvers in the context of whole contract , I want to limit submitters' and approvers' access to a particular transaction Id only ) . I've used Openzeppelin's access control right now .

Thank you in advance .

4
  • How did you define SUBMITTER and APPROVER? How do you check the assigned role?
    – Ismael
    Commented Mar 15, 2023 at 17:10
  • I defined like this earlier : mapping(bytes32 => mapping(address => bool)) public roles; This says role when mapped to an address returns boolean . But after spending a good amount of time thinking what could be done I came up with this : mapping(bytes32 => mapping(address =>mapping(uint256 => bool))) public roles; I want to ask you is the second way of limiting role access okay or I can do something better ? The second way says that bytes32 role is mapped to an address and that address is mapped to a transaction Id , the returned value of this triple nested mapping is a boolean . Commented Mar 15, 2023 at 17:38
  • e.g. first way -> roles[role_in_bytes32][wallet_address] = boolean_value; This way of doing had a problem that the submitter or approver role was being granted in the context of whole contract but I wanted that role should be given to an address depending on transaction Id . So I came up with second way . e.g. second way -> roles[role_in_bytes32][wallet_address][transaction_id] = boolean_value; This way an address will be granted/revoked role depending on a transaction Id . Commented Mar 15, 2023 at 17:40
  • You should use OpenZeppelin AccessControl. It is tested and works. A different approach might work but I can't verify without the code.
    – Ismael
    Commented Mar 15, 2023 at 18:07

2 Answers 2

1
+50

In order to create conditional access control for submitters and approvers based on transaction IDs, you can create a Transaction struct with a mapping for both submitters and approvers:

struct Transaction {
    uint256 numConfirmationsRequired;
    mapping(address => bool) submitters;
    mapping(address => bool) approvers;
}

To assign submitters and approvers for a specific transaction ID, you can use a function like this:

function submit(uint256 _transactionId, address[] memory _submitters, address[] memory _approvers, uint256 _numConfirmationsRequired) external onlyAdmin {
    // Validation checks here...

    Transaction storage t = transactions[_transactionId];
    t.numConfirmationsRequired = _numConfirmationsRequired;

    for (uint i = 0; i < _submitters.length; i++) {
        t.submitters[_submitters[i]] = true;
    }

    for (uint i = 0; i < _approvers.length; i++) {
        t.approvers[_approvers[i]] = true;
    }
}

This approach allows you to limit the access of submitters and approvers for a specific transaction ID

0

This is how I solved my problem :

contract MyContract {
    address public admin; // contract owner

    mapping(bytes32 => mapping(address =>mapping(uint256 => bool))) public roles; // admin , approver , submitter // by doing this an address has a certain role for a certain ID only .
    enter code here

    // 0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42
    bytes32 private constant ADMIN = keccak256(abi.encodePacked("ADMIN")); // admin
    
    // 0x5ff1fb0ce9089603e6e193667ed17164e0360a6148f4a39fc194055588948a31
    bytes32 private constant APPROVER = keccak256(abi.encodePacked("APPROVER")); // approver

    // 0x12b80c3c1a8f58857e0e80e1961585f574502e089ca974eb9fcf05cf904fe012
    bytes32 private constant SUBMITTER = keccak256(abi.encodePacked("SUBMITTER")); // submitter 

    constructor() {
        admin = msg.sender;
        _grantRole(ADMIN,admin,876578867866876); // when contract is deployed ADMIN role is granted to msg.sender i.e. the deployer of the contract 
    }
function _grantRole(bytes32 _role , address _account,uint256 _formId) internal {
    roles[_role][_account][_formId] = true;
    emit GrantRole(_role,_account,_formId);
}

modifier onlyRole(bytes32 _role,uint256 _formId) {
    require(roles[_role][msg.sender][_formId],"not authorized");
    _;
}

function grantRole(bytes32 _role , address _account,uint256 _formId) external onlyRole(ADMIN,876578867866876) { // admin grants a certain role to a certain address
    require(_account != address(0),"address cannot be a zero address");
    _grantRole(_role,_account,_formId);
}

function revokeRole(bytes32 _role , address _account,uint256 _formId) external onlyRole(ADMIN,876578867866876) { // admin can revoke a certain role from a certain address anytime he wants
    require(_account != address(0),"address cannot be a zero address");
    roles[_role][_account][_formId] = false;
    emit RevokeRole(_role,_account,_formId);
}
    event GrantRole(bytes32 indexed role , address indexed account,uint256 _formId);
event RevokeRole(bytes32 indexed role , address indexed account,uint256 _formId);


    }

This approach worked for me . However , I am open to suggestions on how can I make this contract better .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.