canTerminate and validators Example

As explained earlier in this section, the canTerminate and validators contracts, representing key parts of governance rule-sets, are external contracts; they can be freely coded based on the needs of your organization.

On this page are some examples of canTerminate and validators contracts.

Note

Keep in mind that, for them to work correctly, the canTerminate and validators contracts must integrate the check function of the IProposalChecker interface, which is provided by the IProposalManager interface.

If you don't need canTerminate you can pass it empty, in this way, the Proposal can be terminated at any time.

If you pass the validators empty, the Proposal never passes and therefore the code cannot be executed.

canTerminate

canTerminate contracts, of which there can be one or multiple, say whether a Proposal can still be voted on, or that it can be terminated (i.e, the terminate function of that Proposal can be correctly called).

Keep in mind that the canTerminate governance rules follow an "or" logic. This means that even if only one contract returns true, the Proposal can terminate it, even if the others return false. This plays out in the example on this page.

Contract n1- CanBeTerminatedAfterBlockLength:

In this contract, the Proposal can be terminated only after a defined number of blocks have passed since the creation of the Proposal, regardless of how many votes have been cast. For example, if the block length=100 and the Proposal creation block=12345, the terminate function can only be called after block 12445.

contract CanBeTerminatedAfterBlockLength is IProposalChecker {

    string public constant LABEL = 'blockLength';

    string public uri;
    uint256 public value;

    function lazyInit(bytes memory lazyInitData) external returns(bytes memory lazyInitResponseData) {
        require(keccak256(bytes(uri)) == keccak256(""));
        (uri, lazyInitResponseData) = abi.decode(lazyInitData, (string, bytes));
        require(keccak256(bytes(uri)) != keccak256(""));

        value = abi.decode(lazyInitResponseData, (uint256));

        lazyInitResponseData = "";
    }

    function check(address, bytes32, bytes calldata proposalData, address, address) external override view returns(bool) {
        return block.number >= (value + abi.decode(proposalData, (IProposalsManager.Proposal)).creationBlock);
    }
}

The lazyInit initializes:

  • string uri -> represents the uri containing metadata of the contract.

  • uint256 value -> represents the block length since the creation of the proposal after which the terminated function can be called.

Contract n2- CanBeTerminatedWhenHardCapReached:

In this contract, the Proposal can be terminated at any time if the number of accept votes reaches the set hard cap. The hard cap can be a fixed number of votes or a percentage of the total supply of the voting token.

contract CanBeTerminatedWhenHardCapReached is IProposalChecker {

    string public constant LABEL = 'hardCap';

    uint256 public constant ONE_HUNDRED = 1e18;

    string public uri;
    uint256 public value;
    bool public discriminant;

    function lazyInit(bytes memory lazyInitData) external returns(bytes memory lazyInitResponseData) {
        require(keccak256(bytes(uri)) == keccak256(""));
        (uri, lazyInitResponseData) = abi.decode(lazyInitData, (string, bytes));
        require(keccak256(bytes(uri)) != keccak256(""));

        (value, discriminant) = abi.decode(lazyInitResponseData, (uint256, bool));

        lazyInitResponseData = "";
    }

    function check(address, bytes32, bytes calldata proposalData, address, address) external override view returns(bool) {
        IProposalsManager.Proposal memory proposal = abi.decode(proposalData, (IProposalsManager.Proposal));
        uint256 hardCap = discriminant ? _calculatePercentage(_calculateHardCap(proposal), value) : value;
        return proposal.accept >= hardCap;
    }

    function _calculateHardCap(IProposalsManager.Proposal memory proposal) private view returns (uint256 hardCap) {
        (address[] memory collectionAddresses, uint256[] memory objectIds, uint256[] memory weights) = abi.decode(proposal.votingTokens, (address[], uint256[], uint256[]));
        for(uint256 i = 0; i < collectionAddresses.length; i++) {
            hardCap += (_calculateTotalSupply(collectionAddresses[i], objectIds[i]) * weights[i]);
        }
    }

    function _calculatePercentage(uint256 totalSupply, uint256 percentage) private pure returns (uint256) {
        return (totalSupply * ((percentage * 1e18) / ONE_HUNDRED)) / 1e18;
    }

    function _calculateTotalSupply(address collectionAddress, uint256 collectionId) private view returns(uint256) {
        if(collectionAddress == address(0)) {
            return IERC20(address(uint160(collectionId))).totalSupply();
        }
        return Item(collectionAddress).totalSupply(collectionId);
    }
}

The lazyInit initializes:

  • string uri -> represents the uri containing metadata of the contract.

  • uint256 value -> represents the fixed number of votes or the percentage to reach to be able to terminate the proposal.

  • bool discriminant -> boolean value representing if the value represents a fixed number (false) or a percentage of the token total supply (true).

validators

validators contracts, of which there can be one or multiple, state whether a Proposal can pass or not, and therefore whether or not Proposal code can be executed as a one-time Component.

validators checks are subsequent to canTerminate ones. This means that the Proposal must first be able to be terminated (i.e, canTerminate returns true) before it can be executed or not as per the validators logic.

Keep in mind that the validators governance rules follow an "and" logic; i.e all contracts must return true, or the Proposal can't pass.

Contract n1- ValidateQuorum

In this contract, the Proposal can only pass if the number of accept + refuse votes has reached a minimum required amount, i.e. the quorum. The quorum can be a fixed number of votes or a percentage of the total supply of the voting token.

contract ValidateQuorum is IProposalChecker {

    string public constant LABEL = 'quorum';

    uint256 public constant ONE_HUNDRED = 1e18;

    string public uri;
    uint256 public value;
    bool public discriminant;

    function lazyInit(bytes memory lazyInitData) external returns(bytes memory lazyInitResponseData) {
        require(keccak256(bytes(uri)) == keccak256(""));
        (uri, lazyInitResponseData) = abi.decode(lazyInitData, (string, bytes));
        require(keccak256(bytes(uri)) != keccak256(""));

        (value, discriminant) = abi.decode(lazyInitResponseData, (uint256, bool));

        lazyInitResponseData = "";
    }

    function check(address, bytes32, bytes calldata proposalData, address, address) external override view returns(bool) {
        IProposalsManager.Proposal memory proposal  = abi.decode(proposalData, (IProposalsManager.Proposal));
        uint256 quorum = discriminant ? _calculatePercentage(_calculateCensusTotalSupply(proposal), value) : value;
        return ((proposal.accept + proposal.refuse) >= quorum) && (proposal.accept > proposal.refuse);
    }

    function _calculateCensusTotalSupply(IProposalsManager.Proposal memory proposal) private view returns (uint256 censusTotalSupply) {
        (address[] memory collectionAddresses, uint256[] memory objectIds, uint256[] memory weights) = abi.decode(proposal.votingTokens, (address[], uint256[], uint256[]));
        for(uint256 i = 0; i < collectionAddresses.length; i++) {
            censusTotalSupply += (_calculateTotalSupply(collectionAddresses[i], objectIds[i]) * weights[i]);
        }
    }

    function _calculatePercentage(uint256 totalSupply, uint256 percentage) private pure returns (uint256) {
        return (totalSupply * ((percentage * 1e18) / ONE_HUNDRED)) / 1e18;
    }

    function _calculateTotalSupply(address collectionAddress, uint256 collectionId) private view returns(uint256) {
        if(collectionAddress == address(0)) {
            return IERC20(address(uint160(collectionId))).totalSupply();
        }
        return Item(collectionAddress).totalSupply(collectionId);
    }
}

The lazyInit initializes:

  • string uri -> represents the uri containing metadata of the contract.

  • uint256 value -> represents the fixed number of votes or the percentage to reach for the proposal to be valid.

  • bool discriminant -> boolean value representing if the value represents a fixed number (false) or a percentage of the token total supply (true).

Contract n2- CanBeValidBeforeBlockLength:

In this contract, called also "Validation Bomb", the Proposal can be valid if the execution is called before a specific block. For example, if the Proposal start block is 12345 and the validation block is set as 100, the terminate function to execute the Proposal code can only be called before block 12445. After block 12445, the Proposal code cannot be longer executed.

contract CanBeValidBeforeBlockLength is IProposalChecker {

    string public constant LABEL = 'validationBomb';

    string public uri;
    uint256 public value;

    function lazyInit(bytes memory lazyInitData) external returns(bytes memory lazyInitResponseData) {
        require(keccak256(bytes(uri)) == keccak256(""));
        (uri, lazyInitResponseData) = abi.decode(lazyInitData, (string, bytes));
        require(keccak256(bytes(uri)) != keccak256(""));

        value = abi.decode(lazyInitResponseData, (uint256));

        lazyInitResponseData = "";
    }

    function check(address, bytes32, bytes calldata proposalData, address, address) external override view returns(bool) {
        return block.number < (value + abi.decode(proposalData, (IProposalsManager.Proposal)).creationBlock);
    }

The lazyInit initializes:

  • string uri -> represents the uri containing metadata of the contract.

  • uint256 value -> represents the block length to be added to the creation block of the Proposal. After value + Proposal creation block, the Proposal code cannot be executed anymore.

This is, of course, only an example of how canTerminate and validators contracts can be used. Every project can create its own unique rules.

Last updated