OpenST / mosaic-contracts

Mosaic-0: Gateways and anchors on top of Ethereum to scale DApps
https://discuss.openst.org/c/mosaic
MIT License
114 stars 31 forks source link

OST' Composer #739

Closed deepesh-kn closed 5 years ago

deepesh-kn commented 5 years ago

OST' Composer

User Story: As a user, I want to deploy my own contracts in the side chain. To do this I need OST ' on the auxiliary chain.

Scenario: As a company/user, I want to write contracts like custom rules. To get OST' on test net we already have faucet that can provide the OST'. But for mainnet, it is still a manual/centralized work. Instead of this, it will be helpful for the user if they can stake OST on origin chain and mint OST' on an auxiliary chain.

Proposal: “Composer” contract, where (open or whitelisted) facilitator pool can accept (or ignore, and a user can revoke) stakeRequests of OST-> OST’ on mainnet; MVP contract + executable (like a faucet)


Specifications:

Composer contract: Please note: the contract is referred to Composer, but this is not final name. Suggest a better name.

StakerProxy contract:

Please note: the contract is referred to StakerProxy, but this is not final name. Suggest a better name.

Gateways can do one stake - mint process at a time for a given staker address. So the composer contract cannot call gateway.stake() function, it will be not able to serve all stake requests from multiple stakers addresses. So a proxy contract is introduced as a proposal here.

Implementation Proposal

Gateways can do one stake - mint process at a time. Also, the stake function of gateway contract is called by the staker address.

There are multiple ways to implement this

Composer.sol

pragma solidity ^0.5.0;

import "./GatewayInterface.sol";
import "./Organized.sol";
import "./StakerProxy.sol";
import "./EIP20Interface.sol";

contract Composer is Organized {
    // Stake request struct.
    struct StakeRequest {
        uint256 amount;
        address beneficiary;
        uint256 gasPrice;
        uint256 gasLimit;
        uint256 nonce;
        address staker;
        address gateway;
    }

    // Maps staker address to its proxy contract.
    mapping (address => StakerProxy) public stakerProxies;

    // Maps staker address to stake request hash.
    mapping (address => bytes32) public StakeRequestHashes;

    // Maps stake request hash to StakeRequest.
    mapping (bytes32 => StakeRequest) public stakeRequests;

    // constructor
    constructor(
        OrganizationInterface _organization
    ) 
        public 
        Organized(_organization)
    {

    }

    // - Staker can call this function.
    // - Staker needs to approve Composer contract for stake amount.
    function requestStake(
        uint256 _amount,
        address _beneficiary,
        uint256 _gasPrice,
        uint256 _gasLimit,
        uint256 _nonce,
        address _gateway
    )
        external
        returns (
            bytes32 stakeRequestHash_
        )
    {
        // Do basic validations.

        // get stakerProxy
        StakerProxy stakerProxy = stakerProxies[msg.sender];

        // if staker proxy contract addresses does not exists.
        if (address(stakerProxy) == address(0)) {

            // deploy the proxy contract.
            stakerProxy = new StakerProxy(
                address(this), 
                msg.sender
            );
            stakerProxies[msg.sender] = stakerProxy;
        }

        // generate stakeRequestHash_;

        StakeRequest storage stakeRequest = stakeRequests[stakeRequestHash_]; 
        require(stakeRequest.staker == address(0), 'Staker request is already in progress.');

        uint256 nonce = GatewayInterface(_gateway).getNonce(msg.sender);
        require(_nonce == nonce, "Nonce must match.");

        // store stake requests in proxy contract.

        // update the stakeRequests mapping.
        stakeRequests[stakeRequestHash_] = StakeRequest(
            _amount,
            _beneficiary,
            _gasPrice,
            _gasLimit,
            _nonce,
            msg.sender,
            _gateway
        );

        // Transfer the stake amount to this address.
        address token = GatewayInterface(_gateway).token();
        require(
            EIP20Interface(token).transferFrom(msg.sender, address(this), _amount),
            "Stake amount must be transferred to Composer."
        );

        // Emit stake requested event.
    }

    // This can be called by the workers (white listed facilitators).
    // workers/facilitators needs to approve the Composer contract for bounty amount Transfer.
    function acceptStakeRequest(
        bytes32 _stakeRequestHash,
        bytes32 _hashLock
    )
        onlyWorker
        external
        returns (bytes32 messageHash_)
    {
        // do basic validations.

        StakeRequest storage stakeRequest = stakeRequests[_stakeRequestHash]; 
        require(stakeRequest.staker != address(0), 'Staker request must exists.');

        GatewayInterface gateway = GatewayInterface(stakeRequest.gateway);
        delete stakeRequests[_stakeRequestHash]; 

        // get stakerProxy 
        StakerProxy stakerProxy = stakerProxies[msg.sender];
        require(address(stakerProxy) != address(0), 'Staker proxy must exists.');

        // bount amount.
        uint256 bounty = gateway.bounty();
         // Transfer the bounty amount to stakerProxy.
        address token = gateway.token();
        require(
            EIP20Interface(token).transferFrom(msg.sender, address(stakerProxy), bounty),
            "bounty amount must be transferred to stakerProxy."
        );

        address baseToken = gateway.baseToken();
        // transfer the stake amount
        require(
            EIP20Interface(baseToken).transfer(address(stakerProxy), stakeRequest.amount),
            "Stake amount must be transferred to stakerProxy."
        );

        messageHash_ = stakerProxy.acceptStakeRequest(
            stakeRequest.amount,
            stakeRequest.beneficiary,
            stakeRequest.gasPrice,
            stakeRequest.gasLimit,
            stakeRequest.nonce,
            _hashLock,
            stakeRequest.staker,
            stakeRequest.gateway
        );

        // Emit event
    }    

    // For below 2 functions the common code can be extracted.

    function rejectStakeRequest(
        bytes32 _stakeRequestHash
    )
        onlyWorker
        external
    {
        // Basic validations.

        StakeRequest storage stakeRequest = stakeRequests[_stakeRequestHash]; 
        require(stakeRequest.staker != address(0), 'Staker request must exists.');

        address staker = stakeRequest.staker;
        uint256 amount = stakeRequest.amount;

        delete stakeRequests[_stakeRequestHash];

        address token = GatewayInterface(stakeRequest.gateway).token();
        // transfer back the stake amount
        require(
            EIP20Interface(token).transfer(staker, amount),
            "Stake amount must be transferred to staker address."
        );

        // Emit event
    }      

    function revokeStakeRequest(
        bytes32 _stakeRequestHash
    )
        external 
    {
        StakeRequest storage stakeRequest = stakeRequests[_stakeRequestHash]; 
        require(stakeRequest.staker == msg.sender, 'Only staker can call revokeStakeRequest.');

        address staker = stakeRequest.staker;
        uint256 amount = stakeRequest.amount;

        delete stakeRequests[_stakeRequestHash];

        address token = GatewayInterface(stakeRequest.gateway).token();
        // transfer back the stake amount
        require(
            EIP20Interface(token).transfer(staker, amount),
            "Stake amount must be transferred to staker address."
        );

        // Emit event

    }

    function getNonce(
        address _account,
        address _gateway
    ) 
        external 
        view
        returns (uint256 nonce_)
    {
        StakerProxy stakerProxy = stakerProxies[_account];
        if (address(stakerProxy) == address(0)) {
            return 0;
        }
        nonce_ = GatewayInterface(_gateway).getNonce(_account);
    }

}

StakerProxy.sol

pragma solidity ^0.5.0;

import "./GatewayInterface.sol";
import "./EIP20Interface.sol";

contract StakerProxy {

    address  public composer;
    address  public owner;

    modifier onlyComposer {
        require(
            msg.sender == composer,
            "Msg.sender must be composer address."
        );
        _;
    }

    modifier onlyOwner {
        require(
            msg.sender == owner,
            "Msg.sender must be owner address."
        );
        _;
    }

    constructor(
        address _composer, 
        address _owner
    ) 
        public 
    {
        composer = _composer;
        owner = _owner;
    }

     function acceptStakeRequest(
        uint256 _amount,
        address _beneficiary,
        uint256 _gasPrice,
        uint256 _gasLimit,
        uint256 _nonce,
        bytes32 _hashLock,
        address _staker,
        address _gateway
    )
        onlyComposer
        external
        returns (bytes32 messageHash_)
    {
        require(owner == _staker, "Staker must be the owner.");

        // may be we not this step here.
        uint256 nonce = GatewayInterface(_gateway).getNonce(msg.sender);
        require(nonce == _nonce, "nonce must be equal.");

        // Approve gateway for token transfer.
        address token = GatewayInterface(_gateway).token();
        EIP20Interface(token).approve(_gateway, _amount);

        // Approve gateway for bounty transfer.
        address baseToken =GatewayInterface(_gateway).baseToken();
        uint256 bounty = GatewayInterface(_gateway).bounty();
        EIP20Interface(baseToken).approve(_gateway, bounty);

        // This will call stake of gateway contract
        messageHash_ = GatewayInterface(_gateway).stake(
            _amount,
            _beneficiary,
            _gasPrice,
            _gasLimit,
            _nonce,
            _hashLock
        );

    } 
}

questions, notes, thoughts

Facilitator

gulshanvasnani commented 5 years ago

The interface for removeStakerProxy method in StakerProxy contract:

function removeStakerProxy(address _owner) external returns(bool) {
}
deepesh-kn commented 5 years ago

Closing this epic as all tickets are closed.