Skip to content

Commit 0638be4

Browse files
committed
Add Solidity compiler and update contract scripts
1 parent 6e80b33 commit 0638be4

File tree

7 files changed

+123
-70
lines changed

7 files changed

+123
-70
lines changed

.snippets/code/smart-contracts/libraries/ethers-js/checkStorage.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const { ethers } = require('ethers');
22
const { readFileSync } = require('fs');
33
const { join } = require('path');
44

5+
const artifactsDir = join(__dirname, '../contracts');
6+
57
const createProvider = (providerConfig) => {
68
return new ethers.JsonRpcProvider(providerConfig.rpc, {
79
chainId: providerConfig.chainId,
@@ -13,7 +15,7 @@ const createWallet = (mnemonic, provider) => {
1315
return ethers.Wallet.fromPhrase(mnemonic).connect(provider);
1416
};
1517

16-
const loadContractAbi = (contractName, directory = __dirname) => {
18+
const loadContractAbi = (contractName, directory = artifactsDir) => {
1719
const contractPath = join(directory, `${contractName}.json`);
1820
const contractJson = JSON.parse(readFileSync(contractPath, 'utf8'));
1921
return contractJson.abi || contractJson; // Depending on JSON structure
@@ -69,9 +71,9 @@ const providerConfig = {
6971
chainId: 420420422,
7072
};
7173

72-
const mnemonic = 'INSERT_MNEMONIC';
74+
const mnemonic = 'evoke moment pluck misery cheese boy era fresh useful frame resemble cinnamon';
7375
const contractName = 'Storage';
74-
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
76+
const contractAddress = '0x83e43892a98f924706E9DB7917244897dC8b8126';
7577
const newNumber = 42;
7678

7779
interactWithStorageContract(
@@ -80,4 +82,4 @@ interactWithStorageContract(
8082
mnemonic,
8183
providerConfig,
8284
newNumber,
83-
);
85+
);
Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,71 @@
1-
const { compile } = require('@parity/resolc');
2-
const { readFileSync, writeFileSync } = require('fs');
1+
const solc = require('solc');
2+
const { readFileSync, writeFileSync, mkdirSync, existsSync } = require('fs');
33
const { basename, join } = require('path');
44

5-
const compileContract = async (solidityFilePath, outputDir) => {
5+
const ensureDir = (dirPath) => {
6+
if (!existsSync(dirPath)) {
7+
mkdirSync(dirPath, { recursive: true });
8+
}
9+
};
10+
11+
const compileContract = (solidityFilePath, abiDir, artifactsDir) => {
612
try {
713
// Read the Solidity file
814
const source = readFileSync(solidityFilePath, 'utf8');
9-
10-
// Construct the input object for the compiler
15+
const fileName = basename(solidityFilePath);
16+
17+
// Construct the input object for the Solidity compiler
1118
const input = {
12-
[basename(solidityFilePath)]: { content: source },
19+
language: 'Solidity',
20+
sources: {
21+
[fileName]: {
22+
content: source,
23+
},
24+
},
25+
settings: {
26+
outputSelection: {
27+
'*': {
28+
'*': ['abi', 'evm.bytecode'],
29+
},
30+
},
31+
},
1332
};
14-
15-
console.log(`Compiling contract: ${basename(solidityFilePath)}...`);
16-
33+
34+
console.log(`Compiling contract: ${fileName}...`);
35+
1736
// Compile the contract
18-
const out = await compile(input);
19-
20-
for (const contracts of Object.values(out.contracts)) {
21-
for (const [name, contract] of Object.entries(contracts)) {
22-
console.log(`Compiled contract: ${name}`);
37+
const output = JSON.parse(solc.compile(JSON.stringify(input)));
38+
39+
// Check for errors
40+
if (output.errors) {
41+
const errors = output.errors.filter(error => error.severity === 'error');
42+
if (errors.length > 0) {
43+
console.error('Compilation errors:');
44+
errors.forEach(err => console.error(err.formattedMessage));
45+
return;
46+
}
47+
// Show warnings
48+
const warnings = output.errors.filter(error => error.severity === 'warning');
49+
warnings.forEach(warn => console.warn(warn.formattedMessage));
50+
}
51+
52+
// Ensure output directories exist
53+
ensureDir(abiDir);
54+
ensureDir(artifactsDir);
2355

56+
// Process compiled contracts
57+
for (const [sourceFile, contracts] of Object.entries(output.contracts)) {
58+
for (const [contractName, contract] of Object.entries(contracts)) {
59+
console.log(`Compiled contract: ${contractName}`);
60+
2461
// Write the ABI
25-
const abiPath = join(outputDir, `${name}.json`);
62+
const abiPath = join(abiDir, `${contractName}.json`);
2663
writeFileSync(abiPath, JSON.stringify(contract.abi, null, 2));
2764
console.log(`ABI saved to ${abiPath}`);
28-
65+
2966
// Write the bytecode
30-
const bytecodePath = join(outputDir, `${name}.polkavm`);
31-
writeFileSync(
32-
bytecodePath,
33-
Buffer.from(contract.evm.bytecode.object, 'hex'),
34-
);
67+
const bytecodePath = join(artifactsDir, `${contractName}.bin`);
68+
writeFileSync(bytecodePath, contract.evm.bytecode.object);
3569
console.log(`Bytecode saved to ${bytecodePath}`);
3670
}
3771
}
@@ -41,6 +75,7 @@ const compileContract = async (solidityFilePath, outputDir) => {
4175
};
4276

4377
const solidityFilePath = join(__dirname, '../contracts/Storage.sol');
44-
const outputDir = join(__dirname, '../contracts');
78+
const abiDir = join(__dirname, '../abis');
79+
const artifactsDir = join(__dirname, '../artifacts');
4580

46-
compileContract(solidityFilePath, outputDir);
81+
compileContract(solidityFilePath, abiDir, artifactsDir);

.snippets/code/smart-contracts/libraries/ethers-js/deploy.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Deploy an EVM-compatible smart contract using ethers.js
21
const { writeFileSync, existsSync, readFileSync } = require('fs');
32
const { join } = require('path');
43
const { ethers, JsonRpcProvider } = require('ethers');
54

6-
const codegenDir = join(__dirname);
5+
const scriptsDir = __dirname;
6+
const artifactsDir = join(__dirname, '../contracts');
77

8-
// Creates an Ethereum provider with specified RPC URL and chain details
8+
// Creates a provider with specified RPC URL and chain details
99
const createProvider = (rpcUrl, chainId, chainName) => {
1010
const provider = new JsonRpcProvider(rpcUrl, {
1111
chainId: chainId,
@@ -17,9 +17,8 @@ const createProvider = (rpcUrl, chainId, chainName) => {
1717
// Reads and parses the ABI file for a given contract
1818
const getAbi = (contractName) => {
1919
try {
20-
return JSON.parse(
21-
readFileSync(join(codegenDir, `${contractName}.json`), 'utf8'),
22-
);
20+
const abiPath = join(artifactsDir, `${contractName}.json`);
21+
return JSON.parse(readFileSync(abiPath, 'utf8'));
2322
} catch (error) {
2423
console.error(
2524
`Could not find ABI for contract ${contractName}:`,
@@ -32,12 +31,10 @@ const getAbi = (contractName) => {
3231
// Reads the compiled bytecode for a given contract
3332
const getByteCode = (contractName) => {
3433
try {
35-
const bytecodePath = join(
36-
codegenDir,
37-
'../contracts',
38-
`${contractName}.polkavm`,
39-
);
40-
return `0x${readFileSync(bytecodePath).toString('hex')}`;
34+
const bytecodePath = join(artifactsDir, `${contractName}.bin`);
35+
const bytecode = readFileSync(bytecodePath, 'utf8').trim();
36+
// Add 0x prefix if not present
37+
return bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`;
4138
} catch (error) {
4239
console.error(
4340
`Could not find bytecode for contract ${contractName}:`,
@@ -49,7 +46,6 @@ const getByteCode = (contractName) => {
4946

5047
const deployContract = async (contractName, mnemonic, providerConfig) => {
5148
console.log(`Deploying ${contractName}...`);
52-
5349
try {
5450
// Step 1: Set up provider and wallet
5551
const provider = createProvider(
@@ -73,10 +69,11 @@ const deployContract = async (contractName, mnemonic, providerConfig) => {
7369
const address = await contract.getAddress();
7470
console.log(`Contract ${contractName} deployed at: ${address}`);
7571

76-
const addressesFile = join(codegenDir, 'contract-address.json');
72+
const addressesFile = join(scriptsDir, 'contract-address.json');
7773
const addresses = existsSync(addressesFile)
7874
? JSON.parse(readFileSync(addressesFile, 'utf8'))
7975
: {};
76+
8077
addresses[contractName] = address;
8178
writeFileSync(addressesFile, JSON.stringify(addresses, null, 2), 'utf8');
8279
} catch (error) {
@@ -85,11 +82,11 @@ const deployContract = async (contractName, mnemonic, providerConfig) => {
8582
};
8683

8784
const providerConfig = {
88-
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io',
85+
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready
8986
chainId: 420420422,
9087
name: 'polkadot-hub-testnet',
9188
};
9289

93-
const mnemonic = 'INSERT_MNEMONIC';
90+
const mnemonic = 'evoke moment pluck misery cheese boy era fresh useful frame resemble cinnamon';
9491

95-
deployContract('Storage', mnemonic, providerConfig);
92+
deployContract('Storage', mnemonic, providerConfig);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.9;
3+
4+
contract Storage {
5+
uint256 private storedNumber;
6+
7+
event NumberUpdated(uint256 newValue);
8+
9+
function store(uint256 num) public {
10+
storedNumber = num;
11+
emit NumberUpdated(num);
12+
}
13+
14+
function retrieve() public view returns (uint256) {
15+
return storedNumber;
16+
}
17+
}

smart-contracts/libraries/ethers-js.md

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ categories: Smart Contracts, Tooling
66

77
# Ethers.js
88

9-
--8<-- 'text/smart-contracts/polkaVM-warning.md'
10-
119
## Introduction
1210

1311
[Ethers.js](https://docs.ethers.org/v6/){target=\_blank} is a lightweight library that enables interaction with Ethereum Virtual Machine (EVM)-compatible blockchains through JavaScript. Ethers is widely used as a toolkit to establish connections and read and write blockchain data. This article demonstrates using Ethers.js to interact and deploy smart contracts to Polkadot Hub.
@@ -39,7 +37,7 @@ ethers-project
3937
├── abis
4038
│ ├── Storage.json
4139
├── artifacts
42-
│ ├── Storage.polkavm
40+
│ ├── Storage.bin
4341
├── contract-address.json
4442
├── node_modules/
4543
├── package.json
@@ -65,6 +63,17 @@ Next, run the following command to install the Ethers.js library:
6563
npm install ethers
6664
```
6765

66+
Add the Solidity compiler so you can generate standard EVM bytecode:
67+
68+
```bash
69+
npm install --save-dev solc
70+
```
71+
72+
This guide uses `solc` version `{{ dependencies.javascript_packages.solc.version }}`.
73+
74+
!!! tip
75+
The sample scripts use ECMAScript modules. Add `"type": "module"` to your `package.json` (or rename the files to `.mjs`) so that `node` can run the `import` statements.
76+
6877
## Set Up the Ethers.js Provider
6978

7079
A [`Provider`](https://docs.ethers.org/v6/api/providers/#Provider){target=\_blank} is an abstraction of a connection to the Ethereum network, allowing you to query blockchain data and send transactions. It serves as a bridge between your application and the blockchain.
@@ -89,7 +98,7 @@ To interact with Polkadot Hub, you must set up an Ethers.js provider. This provi
8998
To connect to the provider, execute:
9099

91100
```bash
92-
node connectToProvider
101+
node scripts/connectToProvider.js
93102
```
94103

95104
With the provider set up, you can start querying the blockchain. For instance, to fetch the latest block number:
@@ -102,19 +111,7 @@ With the provider set up, you can start querying the blockchain. For instance, t
102111

103112
## Compile Contracts
104113

105-
--8<-- 'text/smart-contracts/code-size.md'
106-
107-
The `revive` compiler transforms Solidity smart contracts into [PolkaVM](/smart-contracts/overview/#native-smart-contracts){target=\_blank} bytecode for deployment on Polkadot Hub. Revive's Ethereum RPC interface allows you to use familiar tools like Ethers.js and MetaMask to interact with contracts.
108-
109-
### Install the Revive Library
110-
111-
The [`@parity/resolc`](https://www.npmjs.com/package/@parity/resolc){target=\_blank} library will compile your Solidity code for deployment on Polkadot Hub. Run the following command in your terminal to install the library:
112-
113-
```bash
114-
npm install --save-dev @parity/resolc
115-
```
116-
117-
This guide uses `@parity/resolc` version `{{ dependencies.javascript_packages.resolc.version }}`.
114+
Polkadot Hub exposes an Ethereum JSON-RPC endpoint, so you can compile Solidity contracts to familiar EVM bytecode with the upstream [`solc`](https://www.npmjs.com/package/solc){target=\_blank} compiler. The resulting artifacts work with any EVM-compatible toolchain and can be deployed through Ethers.js.
118115

119116
### Sample Storage Smart Contract
120117

@@ -140,10 +137,10 @@ The ABI (Application Binary Interface) is a JSON representation of your contract
140137
Execute the script above by running:
141138

142139
```bash
143-
node compile
140+
node scripts/compile.js
144141
```
145142

146-
After executing the script, the Solidity contract will be compiled into the required PolkaVM bytecode format. The ABI and bytecode will be saved into files with `.json` and `.polkavm` extensions, respectively. You can now proceed with deploying the contract to Polkadot Hub, as outlined in the next section.
143+
After executing the script, the Solidity contract is compiled into standard EVM bytecode. The ABI and bytecode are saved into files with `.json` and `.bin` extensions, respectively. You can now proceed with deploying the contract to Polkadot Hub, as outlined in the next section.
147144

148145
## Deploy the Compiled Contract
149146

@@ -154,7 +151,7 @@ You can create a `deploy.js` script in the root of your project to achieve this.
154151
1. Set up the required imports and utilities:
155152

156153
```js title="scripts/deploy.js"
157-
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:1:6'
154+
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:1:4'
158155
```
159156

160157
2. Create a provider to connect to Polkadot Hub:
@@ -166,19 +163,19 @@ You can create a `deploy.js` script in the root of your project to achieve this.
166163
3. Set up functions to read contract artifacts:
167164

168165
```js title="scripts/deploy.js"
169-
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:17:48'
166+
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:17:46'
170167
```
171168

172169
4. Create the main deployment function:
173170

174171
```js title="scripts/deploy.js"
175-
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:49:85'
172+
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:47:82'
176173
```
177174

178175
5. Configure and execute the deployment:
179176

180177
```js title="scripts/deploy.js"
181-
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js:87:95'
178+
--8<-- 'code/smart-contracts/libraries/ethers-js/deploy.js4:92'
182179
```
183180

184181
!!! note
@@ -195,7 +192,7 @@ You can create a `deploy.js` script in the root of your project to achieve this.
195192
To run the script, execute the following command:
196193
197194
```bash
198-
node deploy
195+
node scripts/deploy.js
199196
```
200197
201198
After running this script, your contract will be deployed to Polkadot Hub, and its address will be saved in `contract-address.json` within your project directory. You can use this address for future contract interactions.
@@ -208,12 +205,12 @@ Once the contract is deployed, you can interact with it by calling its functions
208205
--8<-- 'code/smart-contracts/libraries/ethers-js/checkStorage.js'
209206
```
210207
211-
Ensure you replace the `INSERT_MNEMONIC`, `INSERT_CONTRACT_ADDRESS`, and `INSERT_ADDRESS_TO_CHECK` placeholders with actual values. Also, ensure the contract ABI file (`Storage.json`) is correctly referenced.
208+
Ensure you replace the `INSERT_MNEMONIC`, `INSERT_CONTRACT_ADDRESS`, and `INSERT_ADDRESS_TO_CHECK` placeholders with actual values. Also, ensure the contract ABI file (`Storage.json`) is correctly referenced. The script prints the balance for `ADDRESS_TO_CHECK` before it writes and doubles the stored value, so pick any account you want to monitor.
212209
213210
To interact with the contract, run:
214211
215212
```bash
216-
node checkStorage
213+
node scripts/checkStorage.js
217214
```
218215
219216
## Where to Go Next

text/smart-contracts/code-size.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
!!! info "Mind the contract size"
2+
Polkadot Hub enforces Ethereum's 24 KiB contract-size limit (EIP-170) for EVM deployments. Keep contracts modular, share logic through libraries, and make use of compiler optimizations so your bytecode stays within the limit.

variables.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ dependencies:
7171
resolc:
7272
name: '@resolc'
7373
version: 0.2.0
74+
solc:
75+
name: 'solc'
76+
version: 0.8.33
7477
web3_js:
7578
name: '@web3js'
7679
version: 4.16.0

0 commit comments

Comments
 (0)