0

Inspired by this blog: https://medium.com/coinmonks/escrow-service-as-a-smart-contract-the-business-logic-5b678ebe1955 I want to deploy contract once a buyer want to purchase a service. However, I cannot find a proper way to dynamically deploy a contract.

If I use server side javascript to deploy a contract, I must put the private key or mnemonic of the account which deploys the contract and deduct fees. I don't think this is feasible as I want the buyer deploys the contract using buyer's account.

If I use browser side javascript to deploy a contract, I think I already get the account data using MetaMask, so I might use web3 or truffle to deploy a contract. However, truffle seems not support this. I tried new web3.eth.contract(abiArray), but I don't know how to deploy the contract. Very appreciate if anyone could help. Thank you.

  initWeb3: function() {
    if (typeof web3 !== 'undefined') {
      // If a web3 instance is already provided by Meta Mask.
      App.web3Provider = web3.currentProvider;
      web3 = new Web3(web3.currentProvider);
    } else {
      web3 = new Web3(App.web3Provider);
    }
...
    $.getJSON("Election.json", function(election) {
      // Instantiate a new truffle contract from the artifact
      App.contracts.Election = TruffleContract(election);
      // Connect provider to interact with contract. setProvider is a function of TruffleContract class.
      App.contracts.Election.setProvider(App.web3Provider);
...
    web3.eth.defaultAccount = App.account;
    abiArray = App.contracts.Election._json.abi;
    deployedContract = new web3.eth.contract(abiArray);
0

I think I already get the account data using MetaMask, so I might use web3 or truffle to deploy a contract. However, truffle seems not support this.

I sense some conceptual confusion about how this will flow.

Truffle is a developer tool but not part of the production stack, so users would not use it/see it.

Generally, you would have a set of contracts and a front-end. The user would talk directly to the contract(s). So, the deployment would be kicked off by the user. the factory would "see" msg.sender is the user. No middle man.

Generally, users would not compile contracts or have any control over their structure beyond parameters that you decide. They would just ask a factory to deploy something.

This little snippet covers some basic concerns.

pragma solidity 0.5.1;

contract Template {

    address owner;
    uint something;

    constructor(address _owner, uint args) public {
        owner = _owner;
        something = args;
    }       
}  

contract Factory {

    Template[] deployedContracts;

    function createNew(uint arg1) public {
        Template t = new Template(msg.sender, arg1);
        deployedContracts.push(t);
    }
}

In case it isn't clear.

  1. You don't deploy the Template.
  2. You deploy the Factory (with Truffle). Name the source file "Factory.sol".
  3. The "Template" is compiled into the Factory. It's data. This happens because there are variables cast as Type "Template" and in order to resolve that, the compiler will pick up the entirety of Template's bytecode. A contract type is a subset of address.
  4. A user will ask for createNew(args). They have no control over what Factory does. It lets them pass an argument(s) to Template's constructor. In practice, they can have control over parameters but not the logic, which is usually what we want.
  5. Notice how the Factory passes msg.sender that it sees onward to the new contract. The new contract is crafted to do something with that. The example shows only the beginning of giving the creator special status.
  6. Anyone can call the createNew() function.
  7. The Factory is recording the addresses of contracts it created.

From your question, it's unclear to me if the requirements to create a contract are off-chain or on-chain occurrences. You may want to add access control, or work it out so another contract waits for something to happen.

contract Escrow {

    Factory f;

    constructor(address factory) public {
        f = Factory(factory);
    }

    function confirmEngagement() public {
        f.createNew(uint(0));
    }
}

In that arrangement, the Escrow will always "own" the Templates because it is requesting deployment. You could adjust that so it passes msg.sender through and the Factory expects to receive it.

You can add access control to any/all of those functions so only the sources that should use the functions actually do.

Hope it helps.

  • Thank you very much, Rob. In theory, the solution you proposed is what I need. I will come back with further comments. – GoodApple Jul 21 at 7:54
  • I have tried the solution that you proposed. It works fine. I updated to my scenario. I have tested it using Ganache + Truffle + Remix. Seller can initiate a contract from the contract factory with an initiate funding. Seller can abort the contract and get the founding refunded. Thank you very much. Share my revised code in the answer section as below as there's word limit when adding comments. – GoodApple Jul 22 at 1:19
0

Following the advise from Rob Hitchens - B9lab. The revised code is shown as below. Source code also based on Jackson Ng2. Tested it using Ganache + Truffle + Remix. Seller can initiate a contract from the contract factory with an initiate funding. Seller can abort the contract and get the founding refunded. Thank you, Rob. pragma solidity ^0.5.0;

    contract ContractFactory {
        address[] public contracts;
        address public lastContractAddress;

        event newPurchaseContract(
           address contractAddress
        );

        constructor()
            public
        {

        }

        function getContractCount()
            public
            view
            returns(uint contractCount)
        {
            return contracts.length;
        }

        // deploy a new purchase contract
        function newPurchase()
            public
            payable
            returns(address newContract)
        {
            Purchase c = (new Purchase).value(msg.value)(address(msg.sender));
            address cAddr = address(c);
            contracts.push(cAddr);
            lastContractAddress = cAddr;
            emit newPurchaseContract(cAddr);
            return cAddr;
        }

        //tell me a position and I will tell you its address   
        function seePurchase(uint pos)
            public
            view
            returns(address contractAddress)
        {
            return address(contracts[pos]);
        }
    }

    contract Purchase {
        uint public value;
        address payable public seller;
        address payable public buyer;
        enum State { Created, Locked, Inactive }
        State public state;

        // Ensure that `msg.value` is an even number.
        // Division will truncate if it is an odd number.
        // Check via multiplication that it wasn't an odd number.
        constructor(address payable contractSeller) public payable {
            seller = contractSeller;
            value = msg.value / 2;
            require((2 * value) == msg.value);
        }

        modifier condition(bool _condition) {
            require(_condition);
            _;
        }

        modifier onlyBuyer() {
            require(msg.sender == buyer);
            _;
        }

        modifier onlySeller() {
            require(msg.sender == seller);
            _;
        }

        modifier inState(State _state) {
            require(state == _state);
            _;
        }

        event Aborted();
        event PurchaseConfirmed();
        event ItemReceived();

        /// Abort the purchase and reclaim the ether.
        /// Can only be called by the seller before
        /// the contract is locked.
        function abort()
            public
            onlySeller
            inState(State.Created)
        {
            emit Aborted();
            state = State.Inactive;
            seller.transfer(address(this).balance);
        }

        /// Confirm the purchase as buyer.
        /// Transaction has to include `2 * value` ether.
        /// The ether will be locked until confirmReceived
        /// is called.
        function confirmPurchase()
            public
            inState(State.Created)
            condition(msg.value == (2 * value))
            payable
        {
            emit PurchaseConfirmed();
            buyer = msg.sender;
            state = State.Locked;
        }

        /// Confirm that you (the buyer) received the item.
        /// This will release the locked ether.
        function confirmReceived()
            public
            onlyBuyer
            inState(State.Locked)
        {
            emit ItemReceived();
            // It is important to change the state first because
            // otherwise, the contracts called using `send` below
            // can call in again here.
            state = State.Inactive;

            // NOTE: This actually allows both the buyer and the seller to
            // block the refund - the withdraw pattern should be used.
            buyer.transfer(value);
            seller.transfer(address(this).balance);
        }
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.