3

I've looked in some of the resource on calculating the Gas costs like this spreadsheet I found on this thread. But the spreadsheet only have computations gas costs but not storing of hashing. I'm a bit confuse, the way to store IPFS hash in the blockchain contract is also a bit different based on this thread. I thought it would be straight forward as having to store strings or bytes but it needs to be in Struct? Can anyone assist me. * sorry im still new in smart contract programming

3
  • SSTORE for a bytes32 word is 20k gas Oct 24, 2018 at 8:26
  • Thanks for replying. Just want to clarify. from the 2nd thread link. The answer uses struct with bytes and uint right? If i understand correctly its because ipfs uses all 3 of those components right? Does that mean the total gas cost would be storage for 32bytes+uint8 + uint8 and also the ADD operation between all 3? Thanks again Oct 24, 2018 at 8:38
  • You can store it as bytes32 if you base658 encode it. Will post answer Oct 24, 2018 at 8:56

3 Answers 3

9

When storing data on the Ethereum blockchain, each computation task has a cost (in gas unit) and you usually want to reduce as much as possible the cost of the transaction for your application.

You want to store an IPFS hash (or Multihash) (or CID) like QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u in a smart contract

First: Read this to understand multihash


Here is three solutions to store an IPFS hash with the gas cost associated.

Assuming gasPrice: 20 000 000 000 (wei)

1. Store it as a String

Smart contract:

contract IPFSStorage {
    string hash;
    function storeCIDAsString(string _hash) public {
        hash = _hash;
    }
}

Truffle Test:

it('should store the IPFS CID as a string', async () => {

    instance.storeCIDAsString(cid, {'from': accounts[0]}).then(function(txReceipt) {
        console.log('# should store the IPFS CID as a string');

        let gasUsed = txReceipt.receipt.gasUsed;
        console.log("gasUsed: " + gasUsed + " units");

        let gasCost = gasUsed*gasPrice;
        console.log("gasCost (wei): " + gasCost + " wei");

        let gasCostEth = web3.fromWei(gasCost, 'ether')
        console.log("gasCost (ether): " + gasCostEth + " ether");
    }).catch(function (error) {
        console.log(error);
    });
});

Result:

# should store the IPFS CID as a string
gasUsed: 85986 units
gasCost (wei): 1719720000000000 wei
gasCost (ether): 0.00171972 ether

2. Store it as a struct

Smart contract:

contract IPFSStorage {
    struct Multihash {
        bytes32 hash;
        bytes2 hash_function;
        uint8 size;
    }

    Multihash multihash;

    function storeCIDAsStruct(bytes32 _hash, bytes2 _hash_function, uint8 _size) public {

        Multihash memory multihashMemory;
        multihash.hash = _hash;
        multihash.hash_function = _hash_function;
        multihash.size = _size;

        multihash = multihashMemory;
    }
}

Truffle Test:

it('should store the IPFS CID as a struct', async () => {

    let mh = multihashes.fromB58String(Buffer.from(cid))
    let args = {
      hashFunction: '0x' + mh.slice(0, 2).toString('hex'),
      digest: '0x' + mh.slice(2).toString('hex'),
      size: mh.length - 2
    }

    instance.storeCIDAsStruct(args.digest, args.hashFunction, args.size, {'from': accounts[0]}).then(function(txReceipt) {
        console.log('# should store the IPFS CID as a struct');

        let gasUsed = txReceipt.receipt.gasUsed;
        console.log("gasUsed: " + gasUsed + " units");

        let gasCost = gasUsed*gasPrice;
        console.log("gasCost (wei): " + gasCost + " wei");

        let gasCostEth = web3.fromWei(gasCost, 'ether')
        console.log("gasCost (ether): " + gasCostEth + " ether");
    }).catch(function (error) {
        console.log(error);
    });
});

Result:

# should store the IPFS CID as a struct
gasUsed: 55600 units
gasCost (wei): 1112000000000000 wei
gasCost (ether): 0.001112 ether

3. Store it in the event log

Smart contract:

contract IPFSStorage {
    event CIDStoredInTheLog(string _hash);

    function storeCIDInTheLog(string _hash) public {

        emit CIDStoredInTheLog(_hash);
    }
}

Truffle Test:

it('should store the IPFS CID in the logs', async () => {

    instance.storeCIDInTheLog(cid, {'from': accounts[0]}).then(function(txReceipt) {
        console.log('# should store the IPFS CID in the logs');

        let gasUsed = txReceipt.receipt.gasUsed;
        console.log("gasUsed: " + gasUsed + " units");

        let gasCost = gasUsed*gasPrice;
        console.log("gasCost (wei): " + gasCost + " wei");

        let gasCostEth = web3.fromWei(gasCost, 'ether')
        console.log("gasCost (ether): " + gasCostEth + " ether");
    }).catch(function (error) {
        console.log(error);
    });
});

Result:

# should store the IPFS CID in the logs
gasUsed: 27501 units
gasCost (wei): 550020000000000 wei
gasCost (ether): 0.00055002 ether

As you can see, each solution work, the only difference is the gas cost of the transaction for storing an IPFS hash in the Blockchain.

You can find the code here (Truffle project)

4
  • Thanks for replying! Can I just ask. what is number 3 (event log)? Is the event log is something in the block? Like transactions? Will it be preserved in blockchain? Oct 24, 2018 at 9:14
  • Rather than a long comment, here is a good link Oct 24, 2018 at 9:20
  • This is an amazing answer. I will definitely be trying out the struct method you showed. I am surprised just how expensive strings are even compared to the struct you implemented. Oct 24, 2018 at 10:15
  • Storing the IPFS CID as an event with distinct data types for the bytes1: hashFunction, bytes1: size and bytes32: digest improves it even more (by ~10%). May 3, 2020 at 17:45
0

Strings are bad, structs even worse in terms of cost.

This JS code makes an ipfs hash fit into a single EVM word size. Which is the most efficient way of using the EVM.

Tx cost for this is 21k for transaction + 20k for SSTORE (no null value)

Alternatively, you could use Swarm, which is the Ethereum web3 stack P2P file system. The content hashes returned by Swarm are 32 bytes long so they play very nice with the EVM.

https://swarm-guide.readthedocs.io/en/latest/introduction.html

Swarm is a distributed storage platform and content distribution service, a native base layer service of the ethereum web3 stack. The primary objective of Swarm is to provide a sufficiently decentralized and redundant store of Ethereum’s public record, in particular to store and distribute dapp code and data as well as blockchain data. From an economic point of view, it allows participants to efficiently pool their storage and bandwidth resources in order to provide these services to all participants of the network, all while being incentivised by Ethereum.

If you do not wish to write your own wrapper I can recommend using Erebos from the mainframe team which includes a wrapper for PSS (Swarm+Whisper), Postal services over Swarm, as well. https://erebos.js.org/ import bs58 from 'bs58'

// Return bytes32 hex string from base58 encoded ipfs hash,
// stripping leading 2 bytes from 34 byte IPFS hash
// Assume IPFS defaults: function:0x12=sha2, size:0x20=256 bits
// E.g. "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL" -->
// "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
function bytes32FromIpfs(ipfsHash) {
  return (
    "0x" +
    bs58
      .decode(ipfsHash)
      .slice(2)
      .toString("hex")
  )
}

// Return base58 encoded ipfs hash from bytes32 hex string,
// E.g. "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
// --> "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL"
function ipfsFromBytes32(bytes32) {
  // Add our default ipfs values for first 2 bytes:
  // function:0x12=sha2, size:0x20=256 bits
  // and cut off leading "0x"
  const hashHex = "1220" + bytes32.slice(2)
  const hashBytes = Buffer.from(hashHex, "hex")
  const hashStr = bs58.encode(hashBytes)
  return hashStr
}
0

Using an event defined as follows to store the data in the logs improves the gas costs even more. Compared to the best solution that @greag_jeanmart

    event CIDStructStoredInTheLog(
        bytes1 hash_function,
        bytes1 size,
        bytes32 hash
    );

I forked his code and updated it so it runs with the current version of Truffle. Find the code on Github.

gasUsed: 25841 units
gasCost (wei): 516820000000000 wei
gasCost (ether): 0.00051682 ether

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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