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 -cgives17251, which is reasonable- even after more than 15s, the contract bytecode still hasn't been updated
- manually setting the gas limit to
10 000 000doesn't change anything - the deployment transaction receit's status is
1, i.e. successful.