Smart Contracts Archives - Justin Silver https://www.justinsilver.com/tag/smart-contracts/ Technology, Travel, and Pictures Tue, 11 Mar 2025 07:49:09 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.1 https://www.justinsilver.com/wp-content/uploads/2013/06/cropped-apple-touch-icon-160x160.png Smart Contracts Archives - Justin Silver https://www.justinsilver.com/tag/smart-contracts/ 32 32 Deploy Contract With Custom Metadata Hash https://www.justinsilver.com/technology/blockchain/deploy-contract-with-custom-metadata-hash/?utm_source=rss&utm_medium=rss&utm_campaign=deploy-contract-with-custom-metadata-hash https://www.justinsilver.com/technology/blockchain/deploy-contract-with-custom-metadata-hash/#respond Sat, 28 Dec 2024 00:16:07 +0000 https://www.justinsilver.com/?p=5463 When you deploy a Solidity contract the compile has removed the comments from the bytecode, but the last 32 bytes of the bytecode is actually a hash of: hashed bytecode compiler settings contract source...

The post Deploy Contract With Custom Metadata Hash appeared first on Justin Silver.

]]>
AmpedSense.OptimizeAdSpot('AP'); AmpedSense.OptimizeAdSpot('IL'); AmpedSense.OptimizeAdSpot('IR');

When you deploy a Solidity contract the compile has removed the comments from the bytecode, but the last 32 bytes of the bytecode is actually a hash of:

  • hashed bytecode
  • compiler settings
  • contract source including comments

When you verify a contract on EtherScan, or SonicScan, this hash is also checked to make sure the verified contract has the correct license, etc. even if the bytecode itself matches. So… can we deploy a contract with a custom hash that will let us verify contracts with customize comments?

This contract will take a custom metadata hash and update the bytecode of our contract before deploying a new instance of it.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {YourContract} from './YourContract.sol';

contract CustomFactory {
    event ContractDeployed(address indexed contractAddress, bytes32 metadataHash);

    error BytecodeTooShort();

    /**
     * @notice Deploys a new contract with a modified metadata hash.
     * @param newMetadataHash The new metadata hash to append to the bytecode.
     */
    function deployWithCustomMetadata(bytes32 newMetadataHash) external returns (address) {
        // Get the creation code of the contract
        bytes memory bytecode = abi.encodePacked(type(YourContract).creationCode);
        require(bytecode.length > 32, BytecodeTooShort());

        // Replace the last 32 bytes of the bytecode with the new metadata hash
        for (uint256 i = 0; i < 32; i++) {
            bytecode[bytecode.length - 32 + i] = newMetadataHash[i];
        }

        // Deploy the contract with the modified bytecode
        address deployedContract;
        assembly {
            deployedContract := create(0, add(bytecode, 0x20), mload(bytecode))
            if iszero(deployedContract) {
                // if there wsa an error, revert and provide the bytecode
                revert(add(0x20, bytecode), mload(bytecode))
            }
        }

        emit ContractDeployed(deployedContract, newMetadataHash);

        return deployedContract;
    }
}

This is a sample contract with a comment /* YourContract */ we can use to replace with our custom comment.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/* YourContract */

contract YourContract {
    /**
     * @notice Constructor for the contract.
     */
    constructor() {}

    /**
     * @notice Returns the stored message.
     */
    function getMessage() external view returns (string memory) {
        return 'hello world';
    }
}

This is a Hardhat test that loads necessary information from build info and artifacts provided by Hardhat, but you can also create this manually.

// Hardhat test for CustomFactory and YourContract
import * as fs from 'fs/promises';
import * as path from 'path';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { encode as cborEncode } from 'cbor';
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { AddressLike, ContractTransactionResponse } from 'ethers';
import { YourContract } from 'sdk/types';

function calculateMetadataHash(sourceCode: string, comment: string, originalMetadata: any): string {
  const keccak256 = ethers.keccak256;
  const toUtf8Bytes = ethers.toUtf8Bytes;

  // Inject the comment
  const modifiedSourceCode = sourceCode.replace(/\/\* YourContract \*\//gs, comment);
  // console.log('modifiedSourceCode', modifiedSourceCode);

  // Calculate keccak256 of the modified source code
  const sourceCodeHash = keccak256(toUtf8Bytes(modifiedSourceCode));

  // Use original metadata as a base
  const metadata = {
    ...originalMetadata,
    sources: {
      'contracts/YourContract.sol': {
        ...originalMetadata.sources['contracts/YourContract.sol'],
        keccak256: sourceCodeHash,
      },
    },
  };

  // CBOR encode the metadata and calculate the hash
  const cborData = cborEncode(metadata);
  const metadataHash = keccak256(cborData);

  return metadataHash;
}

export const fixture = async () => {
  const Factory = await ethers.getContractFactory('CustomFactory');
  const factory = await Factory.deploy();
  await factory.waitForDeployment();

  const [owner] = await ethers.getSigners();

  return { factory, owner };
};

export async function getDeployedContractAddress(
  tx: Promise<ContractTransactionResponse> | ContractTransactionResponse,
): Promise<AddressLike> {
  const _tx = tx instanceof Promise ? await tx : tx;
  const _receipt = await _tx.wait();
  const _interface = new ethers.Interface([
    'event ContractDeployed(address indexed contractAddress, bytes32 metadataHash)',
  ]);
  const _data = _receipt?.logs[0].data;
  const _topics = _receipt?.logs[0].topics;
  const _event = _interface.decodeEventLog('ContractDeployed', _data || ethers.ZeroHash, _topics);
  return _event.contractAddress;
}

describe('CustomFactory and YourContract', function () {
  it('should deploy a contract with a custom metadata hash', async function () {
    const { factory } = await loadFixture(fixture);

    // Load source code of YourContract from file
    const sourceCodePath = path.join(__dirname, '../contracts/YourContract.sol');
    const sourceCode = await fs.readFile(sourceCodePath, 'utf8');

    const comment = ('/* My Contract */');

    // Load original metadata from build-info
    const buildInfoPath = path.join(__dirname, `../artifacts/build-info`);
    const buildInfoFiles = await fs.readdir(buildInfoPath);

    let buildInfo: any;

    for (const file of buildInfoFiles) {
      const fullPath = path.join(buildInfoPath, file);
      const currentBuildInfo = JSON.parse(await fs.readFile(fullPath, 'utf8'));
      if (currentBuildInfo.output.contracts['contracts/YourContract.sol']) {
        buildInfo = currentBuildInfo;
        break;
      }
    }

    if (!buildInfo) {
      throw new Error('Build info for YourContract not found.');
    }

    const originalMetadata = buildInfo.output.contracts['contracts/YourContract.sol'].YourContract.metadata;

    // Calculate the metadata hash
    const newMetadataHash = calculateMetadataHash(sourceCode, comment, JSON.parse(originalMetadata));

    // Deploy the contract via the Factory
    const tx = await factory.deployWithCustomMetadata(newMetadataHash);
    const deployedAddress = await getDeployedContractAddress(tx);
    // console.log('deployedAddress', deployedAddress);

    // Validate the deployment
    expect(deployedAddress).to.be.properAddress;

    // Interact with the deployed contract
    const YourContract = await ethers.getContractFactory('YourContract');
    const yourContract = YourContract.attach(deployedAddress) as YourContract;

    // Ensure it was initialized properly
    const message = await yourContract.message();
    expect(message).to.equal('hello world');

    // Get the bytecode of the deployed contract
    const deployedBytecode = await ethers.provider.getCode(deployedAddress);

    // Get the last 32 bytes of the deployed bytecode
    const deployedBytecodeHash = deployedBytecode.slice(-64);

    // Validate that the hash matches our new metadata hash
    expect('0x' + deployedBytecodeHash).to.equal(newMetadataHash);
  });
});

The post Deploy Contract With Custom Metadata Hash appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/blockchain/deploy-contract-with-custom-metadata-hash/feed/ 0
Slither & Echidna + Remappings https://www.justinsilver.com/technology/blockchain/slither-echidna-remappings/?utm_source=rss&utm_medium=rss&utm_campaign=slither-echidna-remappings https://www.justinsilver.com/technology/blockchain/slither-echidna-remappings/#comments Mon, 09 Jan 2023 19:16:00 +0000 https://www.justinsilver.com/?p=5357 While testing a project using hardhat and Echidna I was able to run all tests in the project with echidna-test . but was not able to run tests in a specific contract that imported...

The post Slither & Echidna + Remappings appeared first on Justin Silver.

]]>
AmpedSense.OptimizeAdSpot('AP'); AmpedSense.OptimizeAdSpot('IL'); AmpedSense.OptimizeAdSpot('IR');

While testing a project using hardhat and Echidna I was able to run all tests in the project with echidna-test . but was not able to run tests in a specific contract that imported contracts using NPM and the node_modules directory, such as @openzeppelin. When running echidna-test the following error would be returned

> echidna-test path/to/my/Contract.sol --contract Contract

echidna-test: Couldn't compile given file
stdout:
stderr:
ERROR:CryticCompile:Invalid solc compilation Error: Source "@openzeppelin/contracts/utils/Address.sol" not found: File not found. Searched the following locations: "".
 --> path/to/my/Contract.sol:4:1:
  |
4 | import {Address} from '@openzeppelin/contracts/utils/Address.sol';
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To fix this, I added Solc Remappings for Slither and Echidna.

Install Echidna (and Slither)

Make sure that you have Slither and Echidna installed. Follow the install instructions on their site, or on OSX with Homebrew run brew install echidna

Slither Remapping Config

Create a Slither JSON config file – named slither.config.json – to use filter_paths to exclude some directories and provide remappings for node_modules to solc.

{
  "filter_paths": "(mocks/|test/|@openzeppelin/)",
  "solc_remaps": "@=node_modules/@"
}

Slither will pick up the config file automatically.

slither path/to/my/Contract.sol

For multiple remappings using an array of strings for solc_remaps.

{
  "filter_paths": "(mocks/|test/|@openzeppelin/)",
  "solc_remaps": [
    "@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
    "@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/"
  ]
}

Note that if you are using Hardhat or similar for your projects, slither will use it for the compile if a configuration can be found.

slither .

To force a particular compiler, specify it with the command.

slither --compile-force-framework solc ./contracts

Echidna Remapping Config

For Echidna we can create a YAML config file and pass the solc remappings to crytic-compile via cryticArgs.

# provide solc remappings to crytic-compile
cryticArgs: ['--solc-remaps', '@=node_modules/@']

When running echidna-test we can use the --config option to specify the YAML config file and pick up our remappings (and other settings).

echidna-test --config echidna.yaml path/to/my/Contract.sol --contract Contract

Bonus Mythril Remapping Config!

{
  "remappings": ["@openzeppelin/=node_modules/@openzeppelin/"]
}
myth analyze --solc-json mythril.solc.json path/to/my/Contract.sol

The post Slither & Echidna + Remappings appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/blockchain/slither-echidna-remappings/feed/ 2