CoinmanLabs

Posted on Jan 17, 2024Read on Mirror.xyz

为什么你每次部署的合约地址都不一样

     大家好,我是来自Coinmanlabs的Paul,今天想跟大家聊一个问题-部署合约地址的问题。

     首先想问大家一个问题,为什么同样的代码每次部署的地址不一样?

     不知道你是否留意过你写的同一份的智能合约代码,但是你每次部署的时候合约的地址都不一样,你是否想过为什么吗?

     上图是EVM的结构示意图,我们知道以太坊是一个状态转换机,所以每次处理一笔交易,都需要创建一个EVM来执行交易中的数据。这里我们就不深入的讲了,因为今天的重点在于为什么在于合约的地址不一样。

     其实在底层创建的时候,代码会判断当前交易的接收者是否为空,如果为空则意味这是一个创建合约的交易

     代码很简单,就是通过当前合约的创建者地址和其账户中的 Nonce 值,计算出来一个地址值,作为合约的地址。然后将这个地址和其它参数传给 EVM.create 方法。这里唯一需要注意的是由于用到了账户的 Nonce 值,所以同一份合约代码,每次创建合约时得到的合约地址都是不一样的(因为合约是通过发送交易创建,而每发送一次交易 Nonce 值都会改变)。

那现在如果我们想要在多条网络来部署同一份合约代码,就需要去限制我们的Nonce了。

在上图中,交易1和交易2是相同的,因此会将合约部署到两个网络上的相同地址,然而在交易3中,Nonce不同,因此更改了部署的合约地址。

先决条件

     上面我们强调了合约地址需要相同则需要将我们钱包的每条链的Nonce相同。那下面我们就新创建一个钱包进行处理。

     我们所有的操作都在测试网络中部署进行。

步骤1:新建钱包

     很多人对于小狐狸钱包使用仅仅存在于使用钱包进行签名确认交易操作,这样是不够的,这里建议大家可以将小狐狸中的设置相关的参数好好看看。

     由于篇幅有限,我们这里就不深入的讲解太多,这里主要跟大家说两个稍微重要的点,一个是我们在铭刻铭文时候用的比较多的-16进制的开启。另外一个就是测试网络的开启,无论你是撸空投还是进行智能合约的开发都需先在测试网络测试。

当我们将上面两个按钮打开后即可开始新建一个钱包了。

     当我们新建钱包完成后,我们需要将网络切换到相应的网络,我们本次的部署主要在sepolia Test Network、Optimism sepolia、Arbitrum sepolia中进行。

     上述的网络可以在https://chainlist.org/  或者 https://public.blockpi.io/  网站进行添加即可,记住需要将包含测试网络的按钮打开。

步骤2:领取测试币

可以通过下面的渠道进行领取测试币。

1.https://sepoliafaucet.com/

2.https://optimism-faucet.com/

3.https://arbitrum-faucet.com/

我们去到上述的每个网站将我们需要的领取的网络的测试代币进行领取完毕。

步骤3:设置开发环境

当你走到这我默认为你对于一些常用的开发环境是了解的,比如node、vscode的使用。

下面就是代码方面的操作了。

1.新建项目文件目录

mkdir Deterministic-deploy-factory 
cd Deterministic-deploy-factory
mkdir contracts 
mkdir scripts
npm init

记住当需要你填写信息的时候我们只需要使用Enter健回车即可。

2.安装依赖

npm install --save-dev hardhat
npx hardhat
npm install  @nomiclabs/hardhat-ethers 'ethers@^5.0.0'  --save-dev --force
npm install --force   @alch/alchemy-web3 
npm install   dotenv --save --force

因为我们上面使用的dotenv,这是便于我们配置必要的变量操作。所以我们需要在项目的根目录下面新建一个.env的文件,并且将下面的内容写入文件。

API_URL_Sepolia = "{YOUR_ALCHEMY_Sepolia_API_KEY}" 
API_URL_ARBITRUM = "{YOUR_ALCHEMY_ARB-GOERLI-API_KEY}"
API_URL_OPTIMISM = "{YOUR_ALCHEMY_OPT-GOERLI_API_KEY}"
PRIVATE_KEY = "{YOUR_PRIVATE_KEY}"

同时修改我们的配置文件hardhat.config.js:

require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-ethers");
require("dotenv").config();
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const { task } = require("hardhat/config");
const {
  API_URL_Sepolia,
  API_URL_ARBITRUM,
  API_URL_OPTIMISM,
  PRIVATE_KEY,
} = process.env;
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.9",
  networks: {
    hardhat: {},
    Sepolia: {
      url: API_URL_Sepolia,
      accounts: [`0x${PRIVATE_KEY}`],
    },
    arbitrum: {
      url: API_URL_ARBITRUM,
      accounts: [`0x${PRIVATE_KEY}`],
    },
    optimism: {
      url: API_URL_OPTIMISM,
      accounts: [`0x${PRIVATE_KEY}`],
    },
  },
};

当我们完成上面的配置后,就可以进行我们合约的部署了。

步骤4:编写智能合约

我们会编写一个Vault.sol的智能合约,设定只有合约的所有者才可以在指定的时间段存入和提取资金。

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

contract Vault {
    uint public unlockTime;
    address payable public owner;

    event Deposit(uint amount, uint when);
    event Withdrawal(uint amount, uint when);

    constructor(uint _unlockTime) {
        require(
            block.timestamp < _unlockTime,
            "Unlock time should be in the future"
        );
        unlockTime = _unlockTime;
        owner = payable(msg.sender);
    }

    function deposit() payable public {
        emit Deposit(msg.value, block.timestamp);

    }

    function withdraw() public {
        require(block.timestamp >= unlockTime, "You can't withdraw yet");
        require(msg.sender == owner, "you aren't the owner");

        emit Withdrawal(address(this).balance, block.timestamp);

        owner.transfer(address(this).balance);
    }
}

当我们将合约编写完成后,即可在控制台对我们的合约进行编译。

npx hardhat compile

当我们看到上面的提示则已经将我们的合约编译完成了。

步骤5:部署合约

上面我们已经完成了合约的编译工作,下面则只需要编写部署脚本部署到对应的网络即可。

我们在scripts文件夹下面新建一个vaultDeploy.js的脚本并且写入下面的内容。

const main = async () => {
  const unlockTime = "2605659962"; // unlock time must be > deployment time.

  const Vault = await ethers.getContractFactory("Vault");
  const vault = await Vault.deploy(unlockTime);

  await vault.deployed()
  console.log("Vault deployed to:", vault.address);

};

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

当脚本写入完成后,即可使用下面的命令将合约部署上去。

npx hardhat run scripts/vaultDeploy.js --network Sepolia
npx hardhat run scripts/vaultDeploy.js --network arbitrum
npx hardhat run scripts/vaultDeploy.js --network optimism

我们检查结果可以看到现在部署到三个网络的合约地址都是一样的。

但是这样的步骤很麻烦,因为需要每次都将我们的不同网络的Nonce配置为一样的,要不就的新建钱包。那是不是有更简单的方法呢?我们先卖个关子,下期再讲。