0

I have the following deploment script:

require('dotenv').config();
const { ethers } = require("ethers");
const fs = require("fs");
const path = require("path");

const RPC_URL = process.env.RPC_URL.replace(/"/g, "");
const PRIVATE_KEY = process.env.PRIVATE_KEY.replace(/"/g, "");
const ADMIN_ADDRESS = process.env.ADMIN_ADDRESS.replace(/"/g, "");

console.log(`=== Using RPC_URL ${RPC_URL} ===`);
console.log(`=== Using PRIVATE_KEY ${PRIVATE_KEY} ===`);
console.log(`=== Using ADMIN_ADDRESS ${ADMIN_ADDRESS} ===`);

const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

const shipmentManagerJson = JSON.parse(fs.readFileSync("out/ShipmentManager.sol/ShipmentManager.json"));
const shipmentTokenJson = JSON.parse(fs.readFileSync("out/ShipmentToken.sol/ShipmentToken.json"));

const shipmentManagerBytecode = shipmentManagerJson.bytecode.object || shipmentManagerJson.bytecode;
const shipmentManagerABI = shipmentManagerJson.abi;
const shipmentTokenBytecode = shipmentTokenJson.bytecode.object || shipmentTokenJson.bytecode;
const shipmentTokenABI = shipmentTokenJson.abi;

async function main() {
  let currentNonce = await wallet.getNonce();

  // deploy ShipmentToken
  console.log("=== Deploying ShipmentToken ===");
  const ShipmentTokenFactory = new ethers.ContractFactory(shipmentTokenABI, shipmentTokenBytecode, wallet);
  const shipmentToken = await ShipmentTokenFactory.deploy({
    nonce: currentNonce
  });
  await shipmentToken.waitForDeployment();
  const tokenAddress = await shipmentToken.getAddress();
  currentNonce++;
  const tokenCode = await provider.getCode(tokenAddress);
  if(tokenCode.length <= 2) {
    console.error("Error: ShipmentToken deployment failed, no code at address.");
    process.exit(1);
  }

  // deploy ShipmentManager
  console.log("=== Deploying ShipmentManager ===");
  const ShipmentManagerFactory = new ethers.ContractFactory(shipmentManagerABI, shipmentManagerBytecode, wallet);
  const shipmentManager = await ShipmentManagerFactory.deploy({
    nonce: currentNonce
  });
  await shipmentManager.waitForDeployment();
  const managerAddress = await shipmentManager.getAddress();
  currentNonce++;
  const managerCode = await provider.getCode(managerAddress);
  if(managerCode.length <= 2) {
    console.error("Error: ShipmentManager deployment failed, no code at address.");
    process.exit(1);
  }

  console.log("=== Initializing ShipmentToken ===");
  const tx1 = await shipmentToken.initialize(managerAddress, {
    nonce: currentNonce
  });
  const receipt1 = await tx1.wait();
  if (receipt1.status !== 1) {
    console.error("Error: ShipmentToken initialization transaction failed.");
    process.exit(1);
  }
  currentNonce++;
  
  // initialize ShipmentManager
  console.log("=== Initializing ShipmentManager ===");
  const tx2 = await shipmentManager.initialize(ADMIN_ADDRESS, tokenAddress, ADMIN_ADDRESS, {
    nonce: currentNonce
  });
  const receipt2 = await tx2.wait();
  if (receipt2.status !== 1) {
    console.error("Error: ShipmentManager initialization transaction failed.");
    process.exit(1);
  }

  // update process.env with contract addresses
  console.log("=== Updating environment variables ===");
  LOCAL_ENV_PATH = process.env.LOCAL_ENV_PATH || ".env";
  updateEnv(LOCAL_ENV_PATH, "SHIPMENT_MANAGER_CONTRACT_ADDR", managerAddress);
  updateEnv(LOCAL_ENV_PATH, "SHIPMENT_TOKEN_CONTRACT_ADDR", tokenAddress);

  // update backend and frontend envs with contract addresses
  const BACKEND_ENV_PATH = process.env.BACKEND_ENV_PATH;
  const FRONTEND_ENV_PATH = process.env.FRONTEND_ENV_PATH;

  if (BACKEND_ENV_PATH) {
    updateEnv(BACKEND_ENV_PATH, "ADMIN_PRIVATE_KEY", PRIVATE_KEY);
    updateEnv(BACKEND_ENV_PATH, "SHIPMENT_MANAGER_CONTRACT_ADDR", managerAddress);
    updateEnv(BACKEND_ENV_PATH, "SHIPMENT_TOKEN_CONTRACT_ADDR", tokenAddress);
  }
  
  if (FRONTEND_ENV_PATH) {
    updateEnv(FRONTEND_ENV_PATH, "VITE_SHIPMENT_MANAGER_CONTRACT_ADDR", managerAddress);
    updateEnv(FRONTEND_ENV_PATH, "VITE_SHIPMENT_TOKEN_CONTRACT_ADDR", tokenAddress);
  }

  // write ABIs
  console.log("=== Updating ABIs ===");
  const ABI_BACKEND_OUTPUT_PATH = process.env.BACKEND_ABI_PATH;
  const ABI_FRONTEND_OUTPUT_PATH = process.env.FRONTEND_ABI_PATH;

  if (ABI_BACKEND_OUTPUT_PATH) {
    fs.mkdirSync(ABI_BACKEND_OUTPUT_PATH, { recursive: true });
    fs.writeFileSync(path.join(ABI_BACKEND_OUTPUT_PATH, "ShipmentManager.json"), JSON.stringify(shipmentManagerABI, null, 2));
    fs.writeFileSync(path.join(ABI_BACKEND_OUTPUT_PATH, "ShipmentToken.json"), JSON.stringify(shipmentTokenABI, null, 2));
  }

  if (ABI_FRONTEND_OUTPUT_PATH) {
    fs.mkdirSync(ABI_FRONTEND_OUTPUT_PATH, { recursive: true });
    fs.writeFileSync(path.join(ABI_FRONTEND_OUTPUT_PATH, "ShipmentManager.json"), JSON.stringify(shipmentManagerABI, null, 2));
    fs.writeFileSync(path.join(ABI_FRONTEND_OUTPUT_PATH, "ShipmentToken.json"), JSON.stringify(shipmentTokenABI, null, 2));
  }
  
  console.log("");
  console.log("SHIPMENT_TOKEN_CONTRACT_ADDR=" + tokenAddress);
  console.log("SHIPMENT_MANAGER_CONTRACT_ADDR=" + managerAddress);
  console.log("");
  console.log("=== Contracts deployed and ABIs extracted to ===");
  console.log(ABI_BACKEND_OUTPUT_PATH);
  console.log(ABI_FRONTEND_OUTPUT_PATH);
}

function updateEnv(envPath, key, value) {
  let env = fs.readFileSync(envPath, "utf-8").split("\n");
  let found = false;
  env = env.map(line => {
    if (line.startsWith(key + "=")) {
      found = true;
      return `${key}=${value}`;
    }
    return line;
  });
  if (!found) env.push(`${key}=${value}`);
  fs.writeFileSync(envPath, env.join("\n"));
}

main().catch(err => {
  console.error(err);
  process.exit(1);
});

This is the skeleton of the ShipmentToken contract:

contract ShipmentToken is Initializable, ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
  // some code here
}

The deployment script fetches the data from an .env file. When I specify the RPC URL to http://localhost:8545 and I specify an address and private key of an anvil testnet account, the script executes successfully. However, if I use set the RPC URL to https://testnet.hashio.io/api and I use an address and public key from a Hedera Testnet Account, I get the following strange behavior: both ShipmentManager and ShipmentToken appear to deploy successfully and return contract addresses. However, the deployed code is 0x.

The strangest thing of all is that this very same script, with the very same environment variables, worked for the Hedera Testnet two weeks ago.

I would like to mention that I am very much aware, that this is not how you're supposed to deploy upgradeable smart contracts and a proxy should be used. However, the point remains: why does a script that worked two weeks ago, now fail?

EDIT: I have verified several things:

  • forge inspect ShipmentToken bytecode | wc -c gives 17251, which is reasonable
  • even after more than 15s, the contract bytecode still hasn't been updated
  • manually setting the gas limit to 10 000 000 doesn't change anything
  • the deployment transaction receit's status is 1, i.e. successful.

1 Answer 1

1

It turns out that there has been a problem with the Hedera Testnet since July 31st, which results in the exact same issue I am having.

Investigating - We’re investigating an issue on the Hedera Testnet affecting smart contracts. The behaviour was first reported starting 31 July 2025.

Deployments may succeed, but you may experience:

  • Contract bytecode not appearing on Hashscan
  • Read/function calls failing with a BAD_DATA error
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.