Create and Initialize a Covenant Inflation Contract

Any DFO, DAO, individual wallet or customized smart contract can host a Covenant Inflation contract. This flexible design makes it easy for hosts to set rules for distributing rewards (from a reserve or via minting) to farmers, while preventing them from touching farmer tokens or manipulating rewards for locked positions. This secures farmers from exploitation.

Hosts have the power to govern the extension linked to a specific inflation contract, and can call specific functions through the extension. For example, the host can:

  • set a new host

  • create an entry

  • change an entry

  • flushBack tokens back to the extension address

Create a Fixed Inflation Contract

At the heart of Covenant inflation architecture is the Inflation Factory. The Factory is coded with the standard logic of the original model farming contract. Hosts can use it to clone the model to create their own contract. All clones necessarily maintain the model's logic, which allows them to clone and reference the model, and keep track of all event instances.

Hosts use the deploy function to have the Factory clone the model (which, as mentioned, contains the standard logic):

function deploy(bytes memory data) public returns (address contractAddress, bytes memory initResultData) {
    initResultData = _call(contractAddress = _clone(fixedInflationImplementationAddress), data);
    emit FixedInflationDeployed(contractAddress, msg.sender, initResultData);
}

The deploy function deploys a new contract and calls the initialize function for it, passing the encoded data as a parameter.

The contract init function works as follow:

function init(address _extension, bytes memory extensionPayload, FixedInflationEntry memory newEntry, FixedInflationOperation[] memory newOperations) public returns(bytes memory extensionInitResult) {
  • _extension -> extension address to be linked to the contract

  • extensionPayload -> encoded data containing the initialize extension data (if an init extension function is provided); e.g., for the Default extension, this data contains the host address.

  • newEntry -> FixedInflationEntry struct containing the parameters of a contract entry

  • newOperations -> FixedInflationOperation struct containing the parameters of the operations to be performed

If 0x0000000000000000000000000000000000000000 is passed as the _extension address, the init function internally calls the deploy method on the Factory to have it clone the default extension. This means it is not necessary to have previously deployed an extension:

if(_extension == address(0)) {
   _extension = _clone(IFixedInflationFactory(_factory).fixedInflationDefaultExtension());
}

If the extension has an init method it will be called, otherwise not.

if(keccak256(extensionPayload) != keccak256("")) {
    extensionInitResult = _call(_extension, extensionPayload);
}

Then, if the newEntry and newOperations parameters were passed in init function, the _set function is called internally to create all the setups operations of the contract entry:

_set(newEntry, newOperations);

Remember that only one Entry can be referred to each contract.

Send Tokens to the Extension

The extension must be sent and hold an amount of tokens that is at least equal to that needed to execute a contract's operations, otherwise the operations will be correctly created, but will fail at runtime and the extension will automatically become inactive.

If an entry's token are distributed by reserve, the best practice is to deploy the extension and the contract and then immediately send the extension the minimum amount of reward tokens needed to execute the operations.

If the host is a wallet or similar, simply send the amount of tokens manually via a transfer function (or simply transfer the tokens to the extension address using a provider such as Metamask).

If the host is a DFO, DAO or other type of organization, it is necessary to use the relevant procedure to transfer the tokens. If, for example, the host is a DFO, make a proposal to install the manageFixedInflation microservice (you only need to do this once; you won't have to again if you create more inflation Contracts in the future) and enable the extension in the DFO StateHolder (this must be done every time). This will authorize retrieval of the required quantity of tokens from the wallet/treasury DFO to the Inflation Contract extension:

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;

contract ProposalCode {

    string private _metadataLink;

    constructor(string memory metadataLink) {
        _metadataLink = metadataLink;
    }

    function getMetadataLink() public view returns(string memory) {
        return _metadataLink;
    }

    function onStart(address, address) public {
        IMVDProxy proxy = IMVDProxy(msg.sender);
        IStateHolder stateHolder = IStateHolder(proxy.getStateHolderAddress());
        stateHolder.setBool(_toStateHolderKey("fixedinflation.authorized", _toString({0})), true);
        IFixedInflationExtension({0}).setActive(true);
    }

    function onStop(address) public {
    }

    function manageFixedInflation(address sender, uint256, address[] memory tokenAddresses, uint256[] memory transferAmounts, uint256[] memory amountsToMint, address to) public {
        IMVDProxy proxy = IMVDProxy(msg.sender);
        IStateHolder stateHolder = IStateHolder(proxy.getStateHolderAddress());
        require(stateHolder.getBool(_toStateHolderKey("fixedinflation.authorized", _toString(sender))), "Unauthorized action");
        for(uint256 i = 0; i < tokenAddresses.length; i++) {
            IERC20 token = IERC20(tokenAddresses[i]);
            if(amountsToMint[i] > 0) {
                uint256 lastAmount = token.balanceOf(msg.sender);
                token.mint(amountsToMint[i]);
                proxy.flushToWallet(address(token), false, 0);
                if(lastAmount > 0) {
                    proxy.transfer(msg.sender, lastAmount, address(token));
                }
            }
            proxy.transfer(to, transferAmounts[i] + amountsToMint[i], address(token));
        }
    }

    function _toStateHolderKey(string memory a, string memory b) private pure returns(string memory) {
        return _toLowerCase(string(abi.encodePacked(a, ".", b)));
    }

    function _toString(address _addr) private pure returns(string memory) {
        bytes32 value = bytes32(uint256(_addr));
        bytes memory alphabet = "0123456789abcdef";

        bytes memory str = new bytes(42);
        str[0] = '0';
        str[1] = 'x';
        for (uint i = 0; i < 20; i++) {
            str[2+i*2] = alphabet[uint(uint8(value[i + 12] >> 4))];
            str[3+i*2] = alphabet[uint(uint8(value[i + 12] & 0x0f))];
        }
        return string(str);
    }

    function _toLowerCase(string memory str) private pure returns(string memory) {
        bytes memory bStr = bytes(str);
        for (uint i = 0; i < bStr.length; i++) {
            bStr[i] = bStr[i] >= 0x41 && bStr[i] <= 0x5A ? bytes1(uint8(bStr[i]) + 0x20) : bStr[i];
        }
        return string(bStr);
    }
}

interface IMVDProxy {
    function getStateHolderAddress() external view returns(address);
    function getMVDWalletAddress() external view returns(address);
    function transfer(address receiver, uint256 value, address token) external;
    function flushToWallet(address tokenAddress, bool is721, uint256 tokenId) external;
}

interface IStateHolder {
    function setUint256(string calldata name, uint256 value) external returns(uint256);
    function getUint256(string calldata name) external view returns(uint256);
    function getAddress(string calldata name) external view returns(address);
    function setAddress(string calldata varName, address val) external returns (address);
    function getBool(string calldata varName) external view returns (bool);
    function setBool(string calldata varName, bool val) external returns(bool);
    function clear(string calldata varName) external returns(string memory oldDataType, bytes memory oldVal);
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function safeApprove(address spender, uint256 amount) external;
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function decimals() external view returns (uint8);
    function mint(uint256 amount) external;
    function burn(uint256 amount) external;
}

interface IFixedInflationExtension {
    function setActive(bool _active) external;
}

After the extension has received the reward tokens, the transfer from the extension to the contract will be done automatically when an operation is executed, and does not require a manual transfer by the host.

Last updated