Example Contest

Contest where curves represent positions. 1 curve wins and takes share of loser curves' bonded token upon designated oracle's judgement

Description

Below is a contest template where users can bond to contestant curves which mint tokens( unbondabe until contest settled *), winner decided by oracle contract unbonds from loser curves holders of winning token allowed unbond and take share of reserve token(zap) which was unbonded from loser curves

Starting Contest:

  • Deploys with contest uninitialized: status = Uninitialized

  • Anyone can initialize new token:backed curve

  • Owner initializes contest with oracle: status = Initialized

Ending Contest:

  • Owner calls close: status = ReadyToSettle

  • Oracle calls judge to set winning curve: status = Judged

  • Anyone calls settle, contest unbonds from losing curves: status = Settled

  • Holders of winnning token call redeem to retrieve their share of reserve token based on their holding of winning token

*holders of winning token can optionally unbond

Since endpoint specifiers must be unique for any given provider, new curves initialized by onchain token factory providers must have unique endpoint specifiers. Ex PlayerA, PlayerB NOT PlayerA, PlayerA

Note that below imports reflect file structure of zap contract repo

import "../token/TokenFactoryInterface.sol";
import "../token/FactoryTokenInterface.sol";
import "../ownership/ZapCoordinatorInterface.sol";
import "../../platform/bondage/BondageInterface.sol";
import "../../platform/bondage/currentCost/CurrentCostInterface.sol";
import "../../platform/registry/RegistryInterface.sol";
import "../../platform/bondage/currentCost/CurrentCostInterface.sol";

contract SampleContest is Ownable {

    CurrentCostInterface currentCost;
    FactoryTokenInterface public reserveToken;
    ZapCoordinatorInterface public coord;
    TokenFactoryInterface public tokenFactory;
    BondageInterface bondage;

    enum ContestStatus { 
        Uninitialized,    //  
        Initialized,      // ready for buys
        ReadyToSettle,    // ready for judgement 
        Judged,           // winner determined 
        Settled,           // value of winning tokens determined 
        Canceled          // oracle did not respond in time
    }

    address public oracle;    // address of oracle who will choose the winner
    uint256 public ttl;    // time allowed before, close and judge. if time expired, allow unbond from all curves 
    uint256 public expired = 2**256 -1;    // time allowed before, close and judge. if time expired, allow unbond from all curves 
    bytes32 public winner;    // curve identifier of the winner 
    uint256 public winValue;  // final value of the winning token
    ContestStatus public status; //state of contest

    mapping(bytes32 => address) public curves; // map of endpoint specifier to token-backed dotaddress
    bytes32[] public curves_list; // array of endpoint specifiers

    mapping(address => uint8) public redeemed; // map of address redemption state
    address[] public redeemed_list;
    
    event DotTokenCreated(address tokenAddress);
    event Bonded(bytes32 indexed endpoint, uint256 indexed numDots, address indexed sender); 
    event Unbonded(bytes32 indexed endpoint, uint256 indexed numDots, address indexed sender);

    event Initialized(address indexed oracle);
    event Closed();
    event Judged(bytes32 winner);
    event Settled(uint256 winValue, uint256 winTokens); 
    event Reset();

    constructor(
        address coordinator, 
        address factory,
        uint256 providerPubKey,
        bytes32 providerTitle 
    ){
        coord = ZapCoordinatorInterface(coordinator); 
        reserveToken = FactoryTokenInterface(coord.getContract("ZAP_TOKEN"));
        //always allow bondage to transfer from wallet
        reserveToken.approve(coord.getContract("BONDAGE"), ~uint256(0));
        tokenFactory = TokenFactoryInterface(factory);

        RegistryInterface registry = RegistryInterface(coord.getContract("REGISTRY")); 
        registry.initiateProvider(providerPubKey, providerTitle);
        status = ContestStatus.Uninitialized;
    }

// contest lifecycle
 
    function initializeContest(
        address oracleAddress,
        uint256 _ttl
    ) onlyOwner public {
        require( status == ContestStatus.Uninitialized, "Contest already initialized");
        oracle = oracleAddress;
        ttl = _ttl;
        status = ContestStatus.Initialized;
        emit Initialized(oracle);
    }

    function close() onlyOwner {
        status = ContestStatus.ReadyToSettle; 
        expired = block.number + ttl; 
        emit Closed();
    }

    function judge(bytes32 endpoint) {
        require( status == ContestStatus.ReadyToSettle, "not closed" );
        require( msg.sender == oracle, "not oracle");
        winner = endpoint;
        status = ContestStatus.Judged;
        emit Judged(winner);
    }

    function settle() {
        require( status == ContestStatus.Judged, "winner not determined");

        bondage = BondageInterface(coord.getContract("BONDAGE"));
        uint256 dots;
        for( uint256 i = 0; i < curves_list.length; i++) {

            if(curves_list[i] != winner) {
                dots =  bondage.getDotsIssued(address(this), curves_list[i]);  
                if( dots > 0) {
                    bondage.unbond(address(this), curves_list[i], dots);                 
                }  
            }
        } 

        // how many winning dots    
        uint256 numWin =  bondage.getDotsIssued(address(this), winner);  
        // redeemable value of each dot token
        winValue = reserveToken.balanceOf(address(this)) / numWin;
        status = ContestStatus.Settled;
        emit Settled(winValue, numWin);
    }


    //TODO ensure all has been redeemed or enough time has elasped 
    function reset() public {
        require(msg.sender == oracle);
        require(status == ContestStatus.Settled || status == ContestStatus.Canceled, "contest not settled");
        if( status == ContestStatus.Canceled ) {
            require(reserveToken.balanceOf(address(this)) == 0, "funds remain");
        }

        delete redeemed_list;
        delete curves_list;
        status = ContestStatus.Initialized; 
        emit Reset();
    }

/// TokenDotFactory methods

    function initializeCurve(
        bytes32 endpoint, 
        bytes32 symbol, 
        int256[] curve
    ) public returns(address) {
        
        require(curves[endpoint] == 0, "Curve endpoint already exists or used in the past. Please choose new");
        
        RegistryInterface registry = RegistryInterface(coord.getContract("REGISTRY")); 
        require(registry.isProviderInitiated(address(this)), "Provider not intiialized");

        registry.initiateProviderCurve(endpoint, curve, address(this));
        curves[endpoint] = newToken(bytes32ToString(endpoint), bytes32ToString(symbol));
        curves_list.push(endpoint);        
        registry.setProviderParameter(endpoint, toBytes(curves[endpoint]));
        
        DotTokenCreated(curves[endpoint]);
        return curves[endpoint];
    }

    //whether this contract holds tokens or coming from msg.sender,etc
    function bond(bytes32 endpoint, uint numDots) public  {

        require( status == ContestStatus.Initialized, " contest not live"); 

        bondage = BondageInterface(coord.getContract("BONDAGE"));
        uint256 issued = bondage.getDotsIssued(address(this), endpoint);

        CurrentCostInterface cost = CurrentCostInterface(coord.getContract("CURRENT_COST"));
        uint256 numReserve = cost._costOfNDots(address(this), endpoint, issued + 1, numDots - 1);

        require(
            reserveToken.transferFrom(msg.sender, address(this), numReserve),
            "insufficient accepted token numDots approved for transfer"
        );

        reserveToken.approve(address(bondage), numReserve);
        bondage.bond(address(this), endpoint, numDots);
        FactoryTokenInterface(curves[endpoint]).mint(msg.sender, numDots);
        emit Bonded(endpoint, numDots, msg.sender);
    }

    //whether this contract holds tokens or coming from msg.sender,etc
    function unbond(bytes32 endpoint, uint numDots) public {

        require( status == ContestStatus.ReadyToSettle || status == ContestStatus.Settled, "not ready");

        bondage = BondageInterface(coord.getContract("BONDAGE"));
        uint issued = bondage.getDotsIssued(address(this), endpoint);

        //unbond dots
        bondage.unbond(address(this), winner, numDots);

        currentCost = CurrentCostInterface(coord.getContract("CURRENT_COST"));
        //get reserve value to send 
        uint reserveCost = currentCost._costOfNDots(address(this), endpoint, issued + 1 - numDots, numDots - 1);

        FactoryTokenInterface curveToken = FactoryTokenInterface(curves[endpoint]);

        if( status == ContestStatus.ReadyToSettle || status == ContestStatus.Canceled) {
            
            status = ContestStatus.Canceled;
            //oracle has taken too long to judge winner so unbonds will be allowed for all
            require(block.number > expired, "oracle query not expired.");
            require( status == ContestStatus.ReadyToSettle, "contest not ready to settle");

            //unbond dots
            bondage.unbond(address(this), endpoint, numDots);

            //burn dot backed token
            curveToken.burnFrom(msg.sender, numDots);

            require(reserveToken.transfer(msg.sender, reserveCost), "transfer failed");
            Unbonded(endpoint, numDots, msg.sender);
        }

        else {

            require( status == ContestStatus.Settled, " contest not settled"); 
            require(redeemed[msg.sender] == 0, "already redeeemed");
            require(winner==endpoint, "only winners can unbond for rewards"); 

            //reward user's winning tokens unbond value + share of losing curves reserve token proportional to winning token holdings
            uint reward = ( winValue * FactoryTokenInterface(getTokenAddress(winner)).balanceOf(msg.sender) ) + reserveCost;
            
            //burn user's unbonded tokens
            curveToken.burnFrom(msg.sender, numDots);

            reserveToken.transfer(msg.sender, reward);
            redeemed[msg.sender] = 1;

            emit Unbonded(winner, numDots, msg.sender);
        }
    }

    function newToken(
        string name,
        string symbol
    ) 
        public
        returns (address tokenAddress) 
    {
        FactoryTokenInterface token = tokenFactory.create(name, symbol);
        tokenAddress = address(token);
        return tokenAddress;
    }

    function getTokenAddress(bytes32 endpoint) public view returns(address) {
        RegistryInterface registry = RegistryInterface(coord.getContract("REGISTRY")); 
        return bytesToAddr(registry.getProviderParameter(address(this), endpoint));
    }

    // https://ethereum.stackexchange.com/questions/884/how-to-convert-an-address-to-bytes-in-solidity
    function toBytes(address x) public pure returns (bytes b) {
        b = new bytes(20);
        for (uint i = 0; i < 20; i++)
            b[i] = byte(uint8(uint(x) / (2**(8*(19 - i)))));
    }

    //https://ethereum.stackexchange.com/questions/2519/how-to-convert-a-bytes32-to-string
    function bytes32ToString(bytes32 x) public pure returns (string) {
        bytes memory bytesString = new bytes(32);
        bytesString = abi.encodePacked(x);
        return string(bytesString);
    }

    //https://ethereum.stackexchange.com/questions/15350/how-to-convert-an-bytes-to-address-in-solidity
    function bytesToAddr (bytes b) public pure returns (address) {
        uint result = 0;
        for (uint i = b.length-1; i+1 > 0; i--) {
            uint c = uint(b[i]);
            uint to_inc = c * ( 16 ** ((b.length - i-1) * 2));
            result += to_inc;
        }
        return address(result);
    }
}

Last updated