跳到主要内容

部署合约

OXN 上的部署与以太坊几乎一致,只有少量实际调整。本页覆盖生产中真正使用的模式,以及团队第一次踩的坑。

基本要求

OXN 合约部署必须:

  1. evmVersion: "paris" 编译。
  2. 显式设置 gasLimit(标准合约通常 3–5 百万)。
  3. 想让未来调用加密就用 wrapped signer——部署本身无论如何都是明文
  4. 等待交易入块后再记录地址。

Hardhat 部署脚本

scripts/deploy.js
const hre = require("hardhat");

async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deployer:", deployer.address);
console.log("Balance :", (await hre.ethers.provider.getBalance(deployer.address)).toString());

const Contract = await hre.ethers.getContractFactory("MyContract");
const contract = await Contract.deploy(
/* 构造参数 */,
{ gasLimit: 3_000_000n }
);

await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Deployed:", address);
console.log("Tx hash :", contract.deploymentTransaction().hash);

// 验证代码上链
const code = await hre.ethers.provider.getCode(address);
if (code === "0x") {
throw new Error("部署失败——地址上没有代码");
}
console.log("Code size:", (code.length - 2) / 2, "bytes");
}

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

跑:

npx hardhat run scripts/deploy.js --network oxn_testnet

Foundry 部署

forge create src/MyContract.sol:MyContract \
--rpc-url oxn_testnet \
--private-key $PRIVATE_KEY \
--constructor-args "arg1" "arg2" \
--gas-limit 3000000

或用脚本:

script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Script.sol";
import "../src/MyContract.sol";

contract DeployScript is Script {
function run() external {
vm.startBroadcast();
new MyContract(/* args */);
vm.stopBroadcast();
}
}

跑:

forge script script/Deploy.s.sol --rpc-url oxn_testnet --broadcast --gas-limit 3000000

确定性部署(CREATE2)

想跨多个环境得到相同合约地址,用 CREATE2:

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";

contract Deployer {
function deploy(bytes32 salt, bytes memory bytecode) external returns (address) {
return Create2.deploy(0, salt, bytecode);
}

function computeAddress(bytes32 salt, bytes memory bytecode) external view returns (address) {
return Create2.computeAddress(salt, keccak256(bytecode));
}
}

同样的 salt、bytecode、部署方地址,在任何 EVM 链上都能得到相同合约地址——用于让多个网络上合约地址一致。

带机密初始状态的构造函数

构造参数是部署 calldata 的一部分,在 OXN 上是明文(字节码 + 参数永远公开)。所以不能通过构造函数初始化机密。

改为部署后通过一次单独的加密调用初始化:

contract Vault {
address public owner;
bytes32 private secret;

constructor(address _owner) {
owner = _owner;
// 不要这样做: secret = _secret; <-- 会公开在构造 calldata 里
}

function setSecret(bytes32 _secret) external {
require(msg.sender == owner, "not owner");
secret = _secret; // 这次调用的 calldata 是加密的
}
}

明文部署,然后通过 wrapped signer 调用 setSecret。机密从此在存储中加密。

代理模式做可升级

OpenZeppelin 的 Transparent 与 UUPS 代理在 OXN 上无需修改即可用。每个代理合约的存储住在代理地址下;存储加密密钥从这个地址派生,因此升级保留加密状态。

Hardhat + OpenZeppelin upgrades
const { upgrades } = require("hardhat");

const Impl = await ethers.getContractFactory("MyContractV1");
const proxy = await upgrades.deployProxy(Impl, [/* init args */], {
initializer: "initialize",
});
await proxy.waitForDeployment();

升级:

const ImplV2 = await ethers.getContractFactory("MyContractV2");
await upgrades.upgradeProxy(proxyAddress, ImplV2);

记录部署地址

不要跨环境硬编码合约地址。推荐:

scripts/deploy.js(带地址持久化)
const fs = require("fs");
const path = require("path");

// ... 部署 ...

const outputPath = path.join(__dirname, "..", "deployments", `${network.name}.json`);
const existing = fs.existsSync(outputPath) ? JSON.parse(fs.readFileSync(outputPath)) : {};
existing.MyContract = {
address: await contract.getAddress(),
txHash: contract.deploymentTransaction().hash,
deployedAt: new Date().toISOString(),
};
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(existing, null, 2));
console.log("Wrote", outputPath);

deployments/oxn_testnet.json(以及其他环境的对应文件)提交到你的仓库。每个开发者和每次 CI 都从这里读最新地址。

常见部署失败

"合约部署了但 getCode 返回 0x。" 交易成功了但代码没落地。几乎总是 gasLimit 太小。提到 5_000_000n 重来。

"Insufficient balance to pay fees。" 领水龙头补 TBLUAI,或测试网上用 gasPrice: 0n

"合约调用 revert invalid code。" 用错误的 evmVersion 编译了。重新以 paris 编译。

"部署成功但后续调用报 insufficient balance。" 部署方 gas 够部署但不够后续操作。给账户加钱,或用专门的部署账户。

下一步