Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract creation code storage out of gas #255

Closed
KPontes opened this issue Aug 15, 2018 · 6 comments
Closed

Contract creation code storage out of gas #255

KPontes opened this issue Aug 15, 2018 · 6 comments
Labels
discussion Questions, feedback and general information.

Comments

@KPontes
Copy link

KPontes commented Aug 15, 2018

Hi,

I'm getting a Tx Receipt fail with the error message: Warning! Error encountered during contract execution [contract creation code storage out of gas], when trying to deploy a contract creation on Rinkeby with the script deploy-ethers.js bellow.

https://rinkeby.etherscan.io/tx/0xb06380de0996e5b90661e8fcf53bf49cf6f76eae4fc3d5cda1431769f37d17cd

But actually if I go directly on Remix, using an account with Injected Web3, the deployment of the Factory works, and also it works the deployment of child contracts using the Factory.

https://rinkeby.etherscan.io/tx/0xb4f73b2a361693159b1a009967a42947c126a48bc23c5d193933c2415a5b1430

Ps. If I change the script to deploy straight a child futureContract (but not the factory), it ideed works.

Should I set manually some override for Gas ?

Thank you for any help

++++++++++deploy-ethers.js+++++++++++

require("../config/config.js");
const ethers = require("ethers");

// ps. build/FutureContractFactory.json was generated with solc in a previous step
const compiledFactory = require("./build/FutureContractFactory.json");
const NETWORK = process.env.NETWORK;
const bytecode = compiledFactory.bytecode;
const abi = JSON.parse(compiledFactory.interface);

const deploy = function() {
var deployTransaction = ethers.Contract.getDeployTransaction(
"0x" + bytecode, abi
);
var provider = ethers.providers.getDefaultProvider(NETWORK);
var pk = "0x797336cf22a6171b4cb179d6a9c08e5848cbd1748563bc44ea66c506fb0aef8c"; // Only on test network
var wallet = new ethers.Wallet(pk, provider);
var sendPromise = wallet.sendTransaction(deployTransaction);
sendPromise.then(function(transaction) {
console.log("DEPLOYED TO: ", transaction);
});
};

deploy();

++++++++++Solidity Contracts Fabric and Child+++++++++++

pragma solidity 0.4.24;

contract FutureContractFactory {
    address[] public deployedContracts;

function createFutureContract(string _title, uint _contractSize, uint _endDate) public returns (FutureContract) {
    FutureContract newContract = new FutureContract(_title, _contractSize, _endDate, msg.sender);
    deployedContracts.push(newContract);
    return newContract;
}

function getContractsAmount() public view returns (uint) {
    return deployedContracts.length;
}

}

contract FutureContract {
enum State { Created, Settled, Closed }

struct BuyOrder {
    address buyer;
    bytes32 key;
    uint contractsAmount;
    uint depositedEther; //wei
    uint margin;
    uint dealPrice; //* 100
    uint fees; //wei
}

struct SellOrder {
    address seller;
    bytes32 key;
    uint contractsAmount;
    uint depositedEther; //wei
    uint margin;
    uint dealPrice; //* 100
    uint fees; //wei
}

struct Trade {
    bytes32 key;
    bytes32 buyOrderKey;
    bytes32 sellOrderKey;
    uint sellerExitEtherAmount; //wei
    uint buyerExitEtherAmount; //wei
    State status;
}

struct ContractParams {
    address owner;
    uint endDate;
    uint contractSize; //in wei
    string title;
}

ContractParams public contractParams;
mapping(address => uint) public balance;
mapping(bytes32 => Trade) private tradesMap;
mapping(bytes32 => SellOrder) private sellOrdersMap;
mapping(bytes32 => BuyOrder) private buyOrdersMap;

modifier restricted() {
    require(msg.sender == contractParams.owner, "Only allowed to manager");
    _;
}

modifier isValid() {
    require(contractParams.endDate <= now, "Wait for expiration date");
    _;
}

constructor(string _title, uint _contractSize, uint _endDate, address manager) public {
    contractParams.owner = manager;
    contractParams.contractSize = _contractSize;
    contractParams.endDate = _endDate;
    contractParams.title = _title;
}

function createBuyOrder(
    string key,
    uint contractsAmount,
    uint margin,
    uint dealPrice
    ) payable public {

    require(msg.value >= contractsAmount * contractParams.contractSize, "Insufficient deposit");
    require(msg.sender != contractParams.owner, "Manager can not bid");
    require (now < contractParams.endDate, "End date expired");

    bytes32 keyByte = keccak256(bytes(key));
    uint fees = calculateFee(msg.value);
    createBuyOrder(keyByte, contractsAmount, margin, dealPrice, msg.value, fees);
}

function createBuyOrder(
    bytes32 key,
    uint contractsAmount,
    uint margin,
    uint dealPrice,
    uint depositedEther,
    uint fees
    ) private {

    BuyOrder memory newBuyOrder = BuyOrder({
        buyer: msg.sender,
        key: key,
        contractsAmount: contractsAmount,
        dealPrice: dealPrice,
        margin: margin,
        depositedEther: depositedEther,
        fees: fees
    });
    buyOrdersMap[key] = newBuyOrder;
    balance[contractParams.owner] += fees;
}

function getBuyOrder(string key) public view returns(address, uint, uint, uint, uint) {
    bytes32 keyByte = keccak256(bytes(key));
    return (buyOrdersMap[keyByte].buyer, buyOrdersMap[keyByte].contractsAmount, buyOrdersMap[keyByte].depositedEther, buyOrdersMap[keyByte].fees, buyOrdersMap[keyByte].dealPrice);

}

function createSellOrder(
    string key,
    uint contractsAmount,
    uint margin,
    uint dealPrice
    ) payable public {

    require(msg.value >= contractsAmount * contractParams.contractSize, "Insufficient deposit");
    require(msg.sender != contractParams.owner, "Manager can not bid");
    require (now < contractParams.endDate, "End date expired");

    bytes32 keyByte = keccak256(bytes(key));
    uint fees = calculateFee(msg.value);
    createSellOrder(keyByte, contractsAmount, margin, dealPrice, msg.value, fees);
}

function createSellOrder(
    bytes32 key,
    uint contractsAmount,
    uint margin,
    uint dealPrice,
    uint depositedEther,
    uint fees
    ) private {

    SellOrder memory newSellOrder = SellOrder({
        seller: msg.sender,
        key: key,
        contractsAmount: contractsAmount,
        dealPrice: dealPrice,
        margin: margin,
        depositedEther: depositedEther,
        fees: fees
    });
    sellOrdersMap[key] = newSellOrder;
    balance[contractParams.owner] += fees;
}

function getSellOrder(string key) public view returns(address, uint, uint, uint, uint) {
    bytes32 keyByte = keccak256(bytes(key));
    return (sellOrdersMap[keyByte].seller, sellOrdersMap[keyByte].contractsAmount, sellOrdersMap[keyByte].depositedEther, sellOrdersMap[keyByte].fees, sellOrdersMap[keyByte].dealPrice);

}

function createTrade(string tradeKey, string buyOrderKey, string sellOrderKey) restricted public  {
  bytes32 tradeByte = keccak256(bytes(tradeKey));
  bytes32 buyByte = keccak256(bytes(buyOrderKey));
  bytes32 sellByte = keccak256(bytes(sellOrderKey));
  createTrade(tradeByte, buyByte, sellByte);
}

function createTrade(bytes32 tradeKey, bytes32 buyOrderKey, bytes32 sellOrderKey) private {

    require(buyOrdersMap[buyOrderKey].depositedEther > 0, "Must have a buy order created");
    require(sellOrdersMap[sellOrderKey].depositedEther > 0, "Must have a sell order created");
    require(sellOrdersMap[sellOrderKey].dealPrice <= buyOrdersMap[buyOrderKey].dealPrice, "DealPrice does not match");
    require(sellOrdersMap[sellOrderKey].seller != buyOrdersMap[buyOrderKey].buyer, "Buyer and seller must be different");
    require (now <= contractParams.endDate, "End date expired");

    Trade memory newTrade = Trade({
        key: tradeKey,
        buyOrderKey: buyOrderKey,
        sellOrderKey: sellOrderKey,
        sellerExitEtherAmount: 1,
        buyerExitEtherAmount: 1,
        status: State.Created
    });

    tradesMap[tradeKey] = newTrade;
}

function getTrade(string key) public view returns(uint, uint, uint) {
    bytes32 keyByte = keccak256(bytes(key));
    return (
      tradesMap[keyByte].sellerExitEtherAmount,
      tradesMap[keyByte].buyerExitEtherAmount,
      uint(tradesMap[keyByte].status));
}

function getStorageVars() public view returns(uint, uint, string) {
    return (contractParams.endDate, contractParams.contractSize, contractParams.title);
}

function calculateFee(uint etherValue) private pure returns(uint) {
    return (etherValue/1000);
}

}

@ricmoo
Copy link
Member

ricmoo commented Aug 17, 2018

It looks like you are using v3 (the current production release), which sets the gas limit to 1,500,000 if not specified. In v4 (npm install ethers@next), provider.estimateGas is used so this should not be an issue.

To solve this in v3 though, you should be able to:

// Before sending the transaction, increase the gas limit of the transaction
// You should be able to see in remix the gas limit required to deploy it
deployTransaction.gasLimit = 2000000;

Let me know if that still doesn't work.

@ricmoo ricmoo added the discussion Questions, feedback and general information. label Aug 17, 2018
@KPontes
Copy link
Author

KPontes commented Aug 17, 2018

Hi Ricmoo,
You are correct, I'm using v3 and will upgrade.
Nonetheless, I've done some research and realized there is a maxCodeSize for contracts and also block gasLimit. So, the pattern I was using on my contract was not scalable anyways.
This article showed me how to decouple contract logic and data to address gas issues and become more scalable: https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88

So far I've refactored just one feature of the contract, as shown on the code bellow, and it is working. Now I'll load all remaining features and data, to the point it was before, and test again. I'll let you know.

++++++++Interface and Storage Contrac (FutureStorage.sol) +++++++++
pragma solidity 0.4.24;

contract IFutureStorage {
   function createBuyOrder(
    bytes32 key,
    uint contractsAmount,
    uint margin,
    uint dealPrice,
    uint depositedEther,
    uint fees
    ) public;

function getBuyOrder(string key) public view returns(address, uint, uint, uint, uint);
}

contract FutureStorage is IFutureStorage {

struct BuyOrder {
    address buyer;
    bytes32 key;
    uint contractsAmount;
    uint depositedEther; //wei
    uint margin;
    uint dealPrice; //* 100
    uint fees; //wei
}

mapping(bytes32 => BuyOrder) private buyOrdersMap;

function createBuyOrder(
    bytes32 key,
    uint contractsAmount,
    uint margin,
    uint dealPrice,
    uint depositedEther,
    uint fees
    ) public {

    BuyOrder memory newBuyOrder = BuyOrder({
        buyer: msg.sender,
        key: key,
        contractsAmount: contractsAmount,
        dealPrice: dealPrice,
        margin: margin,
        depositedEther: depositedEther,
        fees: fees
    });
    buyOrdersMap[key] = newBuyOrder;
}

function getBuyOrder(string key) public view returns(address, uint, uint, uint, uint) {
    bytes32 keyByte = keccak256(bytes(key));
    return (buyOrdersMap[keyByte].buyer, buyOrdersMap[keyByte].contractsAmount, buyOrdersMap[keyByte].depositedEther, buyOrdersMap[keyByte].fees, buyOrdersMap[keyByte].dealPrice);
}

}

++++++++Business Logic Contrac (FutureContract.sol)+++++++++
pragma solidity 0.4.24;

import "./FutureStorage.sol";

contract FutureContract {

IFutureStorage FS;

struct ContractParams {
    address owner;
    uint endDate;
    uint contractSize; //in wei
    string title;
}

ContractParams public contractParams;
mapping(address => uint) public balance;

constructor(string _title, uint _contractSize, uint _endDate, address manager, address _FS) public {
    contractParams.owner = manager;
    contractParams.contractSize = _contractSize;
    contractParams.endDate = _endDate;
    contractParams.title = _title;
    FS = IFutureStorage(_FS);
}

function createBuyOrder(
    string key,
    uint contractsAmount,
    uint margin,
    uint dealPrice
    ) payable public {

    require(msg.value >= contractsAmount * contractParams.contractSize, "Insufficient deposit");
    require(msg.sender != contractParams.owner, "Manager can not bid");
    require (now < contractParams.endDate, "End date expired");

    bytes32 keyByte = keccak256(bytes(key));
    uint fees = calculateFee(msg.value);
    FS.createBuyOrder(keyByte, contractsAmount, margin, dealPrice, msg.value, fees);
    balance[contractParams.owner] += fees;
}

function getBuyOrder(string key) public view returns(address, uint, uint, uint, uint) {
    return FS.getBuyOrder(key);
}

function getStorageVars() public view returns(uint, uint, string) {
    return (contractParams.endDate, contractParams.contractSize, contractParams.title);
}

function calculateFee(uint etherValue) private pure returns(uint) {
    return (etherValue/1000) ;
}

}

@ricmoo
Copy link
Member

ricmoo commented Aug 17, 2018

Ah yes, if you are trying to deploy to a real network, the block gas limit will restrict the complexity of your contract. There is also a transaction gas limit, which is not part of the protocol, but often enforced by miners, so exceeding this may also result in your transaction not being mined.

There are certainly contracts far more complicated than yours that are on-chain. For example, CryptoKitties. Are you enabling the optimizer for your compiled code? Give the contract above, I would not expect it to exceed the block gas limit.

You can also use delegatecall to spread (and share) code logic across contract addresses (at an extra gas cost for making the external call; 800 gas, I believe).

@KPontes
Copy link
Author

KPontes commented Aug 21, 2018

Hi,
the interface approach worked correctly.
But I've tried to npm ethers@next, and some functions (i.e getDefaultProvider) stopped working so I had to rollback.

Is ethers v4 backward compatible, or would you have some document with the differences?

Thank you Ricmoo

@ricmoo
Copy link
Member

ricmoo commented Aug 21, 2018

It is not backwards compatible, but all features exist. We are working on the documentation, which includes a short migration guide. Most things stayed the same, but for your purpose:

// Version 3
var provider = ethers.providers.getDefaultProvider();

// Version 4
var provider = ethers.getDefaultProvider();

Deploying is a bit different too. In v3, there was a function to create a transaction to deploy, in v4:

// An abstract contract is one without an address; obviously calling most methods on it are not
// possible, much like an abstract class in C++ or Java.
var abstractContract = new ethers.Contract(null, abi, signer);

// This deploys it to chain, using the signer; arg1, arg2, etc are just parameters to the
// constructor; if you don't have any the bytecode is sufficient
var contract = abstractContract.deploy(bytecode, arg1, arg2, etc);

// The contract is probably not mined yet, but you have access to useful things...
console.log(contract.address, contract.deployTransaction);

// You can wait for the contract to deploy... This will reject and error if the deployment
// fails, for example, gas limit was too low, or the constructor called `revert`
await contract.deployed();

Most of the other changes are fairly internal. :)

@ricmoo
Copy link
Member

ricmoo commented Aug 27, 2018

I think this is resolved now, so I'm going to close it.

If this is still an issue though, please feel free to re-open it.

Thanks! :)

@ricmoo ricmoo closed this as completed Aug 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information.
Projects
None yet
Development

No branches or pull requests

2 participants