From this article you can learn:
- What is the role of Truffle's migration contract
Migrations.sol
? - How to deploy multiple contracts together;
- If conditional deployment is performed for different networks and different accounts;
- How to link the library.
Migrations are a set of methods for developers to automate the deployment of data and its supporting structures. They are useful for managing the deployment of new software versions and are therefore not limited to blockchain development.
Truffle migrations allow us to push contracts onto the Ethereum chain (whether it's the local network, testnet, or mainnet), as well as link with other contracts or use initialized contract data.
The really cool thing about migrations is the management of contract addresses on the blockchain. Truffle almost completely abstracts this tedious work.
Prepare
Make sure Truffle Framework and Ganache CLI are installed .
Start
Create (or select) a project directory, then run truffle init
, and output similar to the following:
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
The init command initializes a Truffle project in the current directory. The directory looks like this:
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js
In the contracts
directory, create a contract file: the Storage.sol
code is as follows:
pragma solidity > 0.4.21;
contract Storage {
mapping (string => string) private _store;
function addData(string key, string value) public {
require(bytes(_store[key]).length == 0);
_store[key] = value;
}
function removeData(string key) public returns (string) {
require(bytes(_store[key]).length != 0);
string prev = _store[key];
delete _store[key];
return prev;
}
function changeData(string key, string newValue) public {
require(bytes(_store[key]).length != 0);
_store[key] = newValue;
}
}
Initial Migrations file and deployment rules
You may have seen the sum truffle init
generated at runtime .Migrations.sol
1_initial_migration.js
Initial migration contracts generally do not need to be modified, they track the addresses deployed on the blockchain. Of course, you can also modify the contract file according to your own needs Migrations.sol
and perform some advanced migration management, but you need to keep truffle init
the interface created by the command.
1_initial_migration.js
The migration file only explains how to Migrations.sol
deploy the contract to the corresponding chain.
1_initial_migration.js
The migration file name, the serial number in front, represents the order in which truffle migrate runs the migration files, and 1
represents the first migration file to run (starting from 1). We can create other migration files: 2_mycontract_migration.js
, after each contract is deployed, Truffle will save the migration sequence number to the Migrations contract last_completed_migration
Assuming migrations
just these two migration files, truffle migrate
4 transactions actually occur at runtime:
- run
1_initial_migration.js
to deploy - Write serial number 1 to the contract Migrations
- run
2_mycontract_migration.js
to deploy - Write serial number 2 to the contract Migrations
last_completed_migration
Indicates the last deployed migration, and then adds other migration files: 3_yourcontract.js
When running truffle migrate
, Truffle will first read the last_completed_migration
state variables, see which ones have been deployed before, and deploy last_completed_migration
(all) migration files larger than the serial number, so as to ensure that No duplicate deployments.
Note that if you modify an existing contract and need to redeploy, it truffle migrate
will not be automatically deployed when running directly. You need to add (or modify) a migration file with a higher serial number before running it truffle migrate
.
truffle migrate
Can be -f 序号
used to force re- start migrations one by one (values ignored last_completed_migration
in this case). For example: truffle migrate -f 2
deployment will start from the 2nd migration file.
Migrated related data
In order to deploy a smart contract to the Ethereum blockchain, you first need to write a migration file. In the migrations
directory, create a migration file 2_deploy_contracts.js
. Now the structure of the project is as follows:
.
├── contracts
│ ├── Migrations.sol
│ └── Storage.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
├── truffle-config.js
└── truffle.js
To deploy a contract using migrations, you need to read the contract's artifacts (build) . The build file will describe the network and address of the contract deployment and the functions contained in the contract.
Where does the artifacts (build) data come from?
In the project directory, run truffle compile
, if there are no errors, there will be the following output:
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/Storage.sol...
Writing artifacts to ./build/contracts
Perhaps under different compiler versions, there may be some warnings, as long as there are no errors.
Looking at the directory structure of the project again, there will be an additional build directory, as follows:
.
├── build
│ └── contracts
│ ├── Migrations.json
│ └── Storage.json
├── contracts
│ ├── Migrations.sol
│ └── Storage.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
├── truffle-config.js
└── truffle.js
build
The directory contains two files: Migrations.json
and Storage.json
they correspond to contracts
the two contract files in the directory.
*.json
The files describe their corresponding contracts, including:
- contract name
- Contract ABI (Application Binary Interface — describes all methods of the contract and their corresponding parameters and return values)
- Contract bytecode bytecode (compiled contract data)
- The bytecode deployed by the contract (the latest bytecode that has been deployed on the chain)
- The compiler version that the contract was last compiled with
- A list of networks that have deployed contracts, and the contract addresses on each network.
This file enables Truffle to create a JavaScript wrapper (ie truffle-contract ) to communicate with smart contracts. For example, when called in JavaScript code contract.address
, the Truffle framework *.json
reads addresses from files and lets us easily convert between different contract versions and networks.
write migration file
Armed with this knowledge, let's write our first migration file 2_deploy_contracts.js
with the following code:
// 获取对应的合约文件
var Storage = artifacts.require("./Storage.sol");
// JavaScript export
module.exports = function(deployer) {
// deployer 是用来部署
// 部署
deployer.deploy(Storage);
}
Writing migrations is that simple. In order to run the migration script, run the following command in a terminal:
truffle migrate
But this time, you will get an error:
Error: No network specified. Cannot determine current network.
It means that Truffle cannot find the network to be deployed to. At this time, you can open a new tab in the command line terminal and run ganache-cli
to start a simulated blockchain. After startup, there will be a similar output:
Ganache CLI v6.9.0 (ganache-core: 2.10.0)
Available Accounts
==================
(0) 0x828da2e7b47f9480838f2077d470d39906ad1d8e
(1) 0xa4928865329324560185f1c93b5ebafd7ae6c9e8
(2) 0x957b8b855bed52e11b2d7e9b3e6427771f299f3f
(3) 0xf4b6bcabedaf1ccb3d0c89197c4b961460f1f63d
...
Private Keys
==================
(0) 8729d0f1d876d692f2f454f564042bd11c1e6d0c9b1808954f171f6f7b926fd6
(1) 452dfeee16e5a0e34fa5348f0ef11f39a8b4635e5f454f77fc228ca9598f6997
(2) 9196ad9fd6234f09ee13726cb889dcbc438c15f98e8ff1feb36a93758fa6d10a
(3) fa47edd832e896314544b98d7e297ac2ce2097b49f8a9d7e7ae0e38154db8760
....
HD Wallet
==================
Mnemonic: void august badge future common warfxlb ...
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
A private blockchain has now been built and it runs localhost:8545
on . Now let's configure Truffle to deploy contracts to the network.
Open the truffle-config.js
file and add the following:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*"
}
}
};
The meaning of the above configuration is to deploy the compiled contract to localhost:8545
the network where it is located.
Run it now truffle migrate
and get the following output:
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x06595c0eccde8cb0cf642df07beefea11e3e96bfb470e8tinyxiong6567cecc37aed8
Migrations: 0x6008e9a2c213d51093d0f18536d1aa3b00a7e058
Saving successful migration to network...
... 0x392fb34c755970d1044dc83c56df6e51d5c4d4011319f659026ba27884126d7b
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Storage...
... 0xb8ec575a9f3eca4a11a3f61170231a1816f7c68940d8487e56567adcf5c0a21e
Storage: 0xd8e2af5be9af2a45fc3ee7cdcb68d9bcc37a3c81
Saving successful migration to network...
... 0x15498a1f9d2ce0f867b64cdf4b22ddff56f76xlbcd3d3a92b03b7aa4d881bac
Saving artifacts...
Truffle migrates contracts to the network and saves artifacts. In the file of the build directory , check that it is correct Storage.json
by inspecting the object. networks
You should see something like this:
"networks": {
"1525343635906": {
"events": {},
"links": {},
"address": "0xd8e2af5be9af2a45fc3ee7cdcb68d9bcc37a3c81",
"transactionHash": "0xb8ec575a9f3eca4a11a3f61170231a1816f7c68940d8487e56567adcf5c0a21e"
}
}
1525343635906
is the ID of the network. Both the Ethereum main network and the test network have a fixed ID (the main network is 1).
address
is the address where the contract is deployed. transactionHash
is the transaction hash of the deployed contract.
You will see how to use it later.
Deploy multiple contracts
The real highlight of the Truffle migration is the ability to compile, deploy, and track multiple contracts (as is the case with almost all blockchain projects).
The migration file not only allows us to deploy multiple contracts with a single command, but also allows us to call the functions of the contracts, such as getting the return values of those functions and passing them to subsequent contracts.
We contracts
add a new contract in the directory InfoManager.sol
, the code is as follows:
pragma solidity >0.4.21;
import "./Storage.sol";
contract InfoManager {
Storage private _dataStore;
uint private _lastAdded;
constructor(Storage dataStore) public {
_dataStore = dataStore;
}
function addData(string key, string value) public {
require((now - 1 days) > _lastAdded);
_dataStore.addData(key, value);
}
}
It can be seen that, InfoManager
depending on Storage
the contract, not only that, the constructor of InfoManager also requires the Storage
contract as a parameter.
Rework the deployment to 2_deploy_contracts.js
make it work :InfoManager
var Storage = artifacts.require("./Storage.sol");
var InfoManager = artifacts.require("./InfoManager.sol");
module.exports = function(deployer) {
// 部署 Storage
deployer.deploy(Storage)
// 等待、直到合约部署完成
.then(() => Storage.deployed())
// 传递 Storage 合约地址,部署 InfoManager 合约
.then(() => deployer.deploy(InfoManager, Storage.address));
}
The syntax for deployment is:
...
deployer.deploy(`ContractName`, [`constructor params`]) // 返回一个 promise
...
Since it deploy(...)
returns a promise, we can handle it any way we like, but don't notice that in the deployment file, it's not supported yet async
.
We can also customize the calling function after deploying the contract. For example, the migration can also be like this:
deployer.deploy(Storage)
.then(() => Storage.deployed())
.then((instance) => {
instance.addData("Hello", "world")
}).then(() => deployer.deploy(InfoManager, Storage.address));
In this way, before deploying InfoManager
, Storage
add a piece of data.
This trick is useful because sometimes interdependent contracts may need to be written outside of the constructor.
Deploy according to the network
Different deployments can also be performed according to different networks. This is useful both for using the simulated chain's data during the development phase and for the mainnet launch using the deployed mainnet contract as an input parameter to the contract.
module.exports
Extend a parameter by exporting a function network
:
module.exports = function(deployer, network) {
if (network == "live") {
// do one thing
} else if (network == "development") {
// do other thing
}
}
Select an account to deploy
module.exports
The default function can also expose an account parameter, these accounts are "exposed" by the Ethereum node or wallet provider, see the following example:
module.exports = function(deployer, network, accounts) {
var defaultAccount;
if (network == "live") {
defaultAccount = accounts[0]
} else {
defaultAccount = accounts[1]
}
}
link library
You can also deployer.link(...)
link to an already existing (deployed) library by:
...
deployer.deploy(MyLibrary);
deployer.link(MyLibrary, MyContract);
deployer.deploy(MyContract);
...
in conclusion
By using these techniques, it is possible to automate much of the blockchain deployment work and reduce a lot of repetitive work involved in developing decentralized applications.
Reference article: