Create and Initialize a Farming Contract

At the heart of Covenant farming architecture is the Farming Factory, which is encoded with the standard logic of the model farming contract. Hosts use the Factory to clone and customize the model to create their own contracts.

Covenant Farming v1.0 contracts can contain multiple 'Generation 1' Free Farming setups and Locked Farming setups. Farming v1.0 supports the AMM Aggregator so that setups can be created using all AMMs supported by the AMM Aggregator such as Uniswap v2, Sushiswap, Mooniswap, Balancer.

Hosting

Any DFO, DAO, dApp, individual wallet or customized smart contract can host Covenant farming contracts. The flexible design of the contracts makes it easy for hosts to set rules for distributing rewards to farmers (from a reserve or by minting), while preventing hosts from touching farmer tokens or manipulating rewards for Locked positions. This secures farmers from exploitation.

A host has the power to control the extension linked to the hosted farming contract, allowing them to call specific functions. For example, they can:

  • set a new host

  • update the treasury address

  • update setups

  • deactivate setups

Create a Farming Contract

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(farmMainImplAddress), data);
    emit FarmMainDeployed(contractAddress, msg.sender, initResultData);
}

When the Factory does this, it calls the initialize function for the farming contract, passing the encoded data as a data parameter.

The init function works as follow:

function init(address extension, bytes memory extensionInitData, address orchestrator, address rewardTokenAddress, bytes memory farmingSetupInfosBytes) public returns(bytes memory extensionReturnCall)
  • extension -> the address of the deployed extension to be linked to the contract

  • extensionInitData -> the encoded initialize extension data (available if an init extension function is provided). The default extension's init data contains three parameters: a boolean value representing if the reward token is minted (true) or from reserve (false); the host address; and the treasury address.

  • orchestrator -> the address of the Item Orchestrator

  • rewardTokenAddress -> the address of the reward token of the contract. Each contract has one.

  • farmingSetupInfoBytes -> ABI-encoded data of the contract's setups, which can be created directly during the initialization phase.

The extension (whether default or custom) must be deployed before deploying the farming contract through the Factory's deploy function. This allows the deployed extension to be linked to the Farming contract through the Farming contract init function. As noted above, the deployed extension doesn't need to have been initialized before this (and thus an init function is not provided), but can be done directly through the contract's init function, passing the extensionInitData in the payload. If the extension has an init function, it will be called, otherwise not:

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

The init function creates the contract reference Item collection by calling the createNative method using the Item Orchestrator. So, each contract has its own native Item collection. This collection is used to manage farm tokens related to the liquidity management of locked setups:

(_farmTokenCollection,) = IEthItemOrchestrator(orchestrator).createNative(abi.encodeWithSignature("init(string,string,bool,string,address,bytes)", "Covenants Farming", "cFARM", true, IFarmFactory(_factory).getFarmTokenCollectionURI(), address(this), ""), "");

If the farmingSetupInfoBytes parameter was passed in init function, the _setOrAddFarmingSetupInfo function is called internally to create all the passed setups:

if(farmingSetupInfosBytes.length > 0) {
    FarmingSetupInfo[] memory farmingSetupInfos = abi.decode(farmingSetupInfosBytes, (FarmingSetupInfo[]));
    for(uint256 i = 0; i < farmingSetupInfos.length; i++) {
        _setOrAddFarmingSetupInfo(farmingSetupInfos[i], true, false, 0);
}

Otherwise setups can be created directly using the setFarmingSetups function (look at the Add New Farming Setups to a Contract section to see how to customize setups and create new ones).

Send Reward Tokens to the Extension

The extension must receive at least enough tokens needed to reward every setup created for the contract. If not, the setups will be correctly created within the contract, but cannot be activated.

If any reward tokens are from a reserve, it is best practice to deploy the contract and the extension and then immediately send to the extension the minimum amount of reward tokens to activate all setups.

If the host is a wallet or similar, simply send the required amount of reward 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 types of organization, you must use the appropriate procedure to transfer the tokens. For example, if the host is a DFO, make a proposal to install in it the manageFarming microservice (only the first time, not if you create more contracts in the future), and enable the extension in the DFO StateHolder (this must be done every time). This will authorize you to retrieve the required amount of reward tokens from the DFO's treasury to be sent to the farming contract's 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("farming.authorized", _toString(/*extension_address*/)), true);
    }

    function onStop(address) public {
    }

    function manageFarming(address sender, uint256, bool transfer, address erc20TokenAddress, address to, uint256 value, bool byMint) public {
        IMVDProxy proxy = IMVDProxy(msg.sender);
        IStateHolder stateHolder = IStateHolder(proxy.getStateHolderAddress());
        require(stateHolder.getBool(_toStateHolderKey("farming.authorized", _toString(sender))), "Unauthorized action");
        IERC20 token = IERC20(erc20TokenAddress);
        if(transfer) {
            if(byMint) {
                uint256 lastAmount = token.balanceOf(msg.sender);
                token.mint(value);
                proxy.flushToWallet(erc20TokenAddress, false, 0);
                if(lastAmount > 0) {
                    proxy.transfer(msg.sender, lastAmount, address(token));
                }
            }
            proxy.transfer(to, value, erc20TokenAddress);
        } else {
            if(erc20TokenAddress == address(0)) {
                return;
            }
            token.transferFrom(sender, byMint ? address(this) : proxy.getMVDWalletAddress(), value);
            if(byMint) {
                token.burn(value);
            }
        }
    }

    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;
}

After the extension has received the tokens, the host does not need to then manually transfer them to the farming contract. This will happen automatically when the setup(s) are activated.

See the Activate/Deactivate section for more details.

Last updated