Permission

The swissKnife library provides a bunch of other cool stuff, like ACL (Access-Control List) permission leveling.

The subjectIsAuthorizedFor and authorizedOnly modifiers here are provided by the LazyInitCapableElement contract. They can be used to build sophisticated permission systems for your applications and protocols, setting which addresses or contracts can perform actions or trigger some specific methods in all contracts that integrate the LazyInitCapableElement.

You can use these utility functions to create complex, multi-layer permission systems.

How Permission Levels Work

Multi-layer permission systems allow you to define who can and who can’t perform actions on your smart contracts. You can even define who can and who can’t use each of a contract’s specific internal functions.

This is possible with any contract that integrates LazyInitCapable Element, and thus also DynamicMetadataCapableElement and the standard Factory contract.

subjectIsAuthorizedFor

This is a view function:

function subjectIsAuthorizedFor(address subject, address location, bytes4 selector, bytes calldata payload, uint256 value) public override virtual view returns(bool) {
    (bool chidlElementValidationIsConsistent, bool chidlElementValidationResult) = _subjectIsAuthorizedFor(subject, location, selector, payload, value);
    if(chidlElementValidationIsConsistent) {
        return chidlElementValidationResult;
    }
    if(subject == host) {
        return true;
    }
    if(!host.isContract()) {
        return false;
    }
    (bool result, bytes memory resultData) = host.staticcall(abi.encodeWithSelector(ILazyInitCapableElement(host).subjectIsAuthorizedFor.selector, subject, location, selector, payload, value));
    return result && abi.decode(resultData, (bool));
}

Its purpose is to define that an address (subject) can call a function (selector) to trigger a behavior of a contract (location) passing a value and a payload.

This is a permission layer. You can give it a quantitative aspect too, so that the address can only operate within a defined balance range.

How the subjectIsAuthorizedFor function works

The function takes all the input parameters and passes them to the _subjectIsAuthorizedFor function. This is a virtual function provided by the LazyInitCapableElement contract so that its behavior can be overridden by an inheriting contract to create customized permission logic.

function _subjectIsAuthorizedFor(address, address, bytes4, bytes calldata, uint256) internal virtual view returns(bool, bool) {

The function has two boolean outputs, the first is childElementValidationIsConsistent and the second is childElementValidationResult. Since in Solidity an undefined or a null is expressed as false, the first output represents if the result is consistent. In this way, a null or undefined result doesn't affect the second childElementValidationResult. In fact, the second parameter represents the "real" result of the function.

So the subjectIsAuthorizedFor has only one output boolean value that is the permission result:

  • If childElementValidationIsConsistent , the output result is represented by childElementValidationResult of the _subjectIsAuthorizedFor.

  • if the subject is equal to the host, the output result is true.

  • if the host address is not a contract, the output result is false.

  • If the host address is a contract and it implements the lazyInitCapableElement, the subjectIsAuthorizedFor function on that host address is called.

authorizedOnly

subjectIsAuthorizedFor is used to build the authorizedOnly modifier:

modifier authorizedOnly {
    require(_authorizedOnly(), "unauthorized");
    _;
}

Here, _authorizedOnly is simply the result of the subjectIsAuthorizedFor view function:

function _authorizedOnly() internal returns(bool) {
    return subjectIsAuthorizedFor(msg.sender, address(this), msg.sig, msg.data, msg.value);
}

By overriding _subjectIsAuthorizedFor in your inheriting child contract, you can create a customized authorizedOnly modifier, which you can use in your application or protocol to distinguish who can do what, e.g. change a host, set an organization’s component, change the model contract to clone in a Factory, etc.

Integrate Your Smart Contract With a Permission Level

You don’t have to build a complex permissions system, and if you choose not to, your contract will follow the default behavior where only the host address (which is defined at the LazyInitCapableElement level) can call functions integrated with the authorizedOnly modifier; no further restrictions will be applied.

In fact, if the _subjectIsAuthorizedFor is not overridden, the default result of chidlElementValidationIsConsistent is false and so the if

if(chidlElementValidationIsConsistent) {
    return chidlElementValidationResult;
}

is skipped.

If you want to customize your contract’s permission levels coding on your own, build your own custom authorizedOnly modifier and use it to override the _subjectIsAuthorizedFor function.

For some examples of customized permission systems in Factory-compliant contracts, see below.

Example

Here is a practical example of a smart contract that integrates customized permission levels: the Organization contract.

Only active components attached to the Organization have writing rights on other components of the Organization, as well as on the Organization itself. The authorizedOnly modifier is customized to represent this behavior.

As established earlier, authorizedOnly is built by the _subjectIsAuthorizedFor function; so, an organization must implement _subjectIsAuthorizedFor:

function _subjectIsAuthorizedFor(address subject, address location, bytes4 selector, bytes calldata, uint256) internal override view returns(bool, bool) {
    if(location == address(this) && selector == this.setHost.selector) {
        return (true, false);
    }
    return (true, isActive(subject));
}

The first part of the function says that no one can call the setHost function to change the host of an Organization.

The second part says the function’s outputs, in all other cases, are composed as:

(true, isActive(subject))

Here, the true parameter refers to childElementValidationIsConsistent, and the second one refers to childElementValidationResult. Keep in mind that if childElementValidationIsConsistent is true, the final result of subjectIsAuthorizedFor is represented by childElementValidationResult.

In this case, childElementValidationResult is the result of the isActive function. This function checks if the subject is an active key and therefore an active Component. In this case, the result will be true. But if the subject doesn’t correspond to an active key, the result will be false, and it will have no permission to act on the Organization.

The authorizedOnly modifier represents the result behavior explained above:

modifier authorizedOnly {
    require(_authorizedOnly(), "unauthorized");
    _;
}
function _authorizedOnly() internal returns(bool) {
    return subjectIsAuthorizedFor(msg.sender, address(this), msg.sig, msg.data, msg.value);
}

authorizedOnly will only ever be an active Component expressed in the _subjectIsAuthorizedFor function.

To summarize: the _subjectIsAuthorizedFor and authorizedOnly modifiers can be customized by developers as they see fit; i.e, as required by the requirements and business logic of their protocols and applications.

Last updated