Skip to content

Commit b0da943

Browse files
sarahschwartzuF4No
andauthored
docs: interop + multichain ecosystem tutorials (#150)
Adds two new tutorials: 1. Setup a multichain ecosystem with chains that settle on Gateway 2. Build a crosschain defi app using interop messages --------- Co-authored-by: Antonio <tonioufa@gmail.com>
1 parent 502f3ef commit b0da943

37 files changed

Lines changed: 3704 additions & 10 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Node modules
2+
/node_modules
3+
4+
# Compilation output
5+
/dist
6+
7+
# pnpm deploy output
8+
/bundle
9+
10+
# Hardhat Build Artifacts
11+
/artifacts
12+
13+
# Hardhat compilation (v2) support directory
14+
/cache
15+
16+
# Typechain output
17+
/types
18+
19+
# Hardhat coverage reports
20+
/coverage
21+
22+
# Environment
23+
.env
24+
25+
# Deployments
26+
ignition/deployments

code/interop-messages/contracts/bun.lock

Lines changed: 721 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.27;
3+
4+
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
5+
import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol';
6+
import { IMessageVerification } from '@matterlabs/zksync-contracts/contracts/l1-contracts/state-transition/chain-interfaces/IMessageVerification.sol';
7+
import { L2Message } from '@matterlabs/zksync-contracts/contracts/l1-contracts/common/Messaging.sol';
8+
9+
contract InteropToken is ERC20, Ownable {
10+
// mapping of chain IDs to the approved staking contract address
11+
mapping(uint256 => address) public approvedStakingContracts;
12+
13+
// mapping of addresses to if they have minted tokens or not
14+
mapping(address => bool) public addressesThatMinted;
15+
16+
// mapping of chain IDs to number of adresses that have minted some tokens
17+
mapping(uint256 => uint256) public rewardsByChain;
18+
19+
// the chain ID with the most rewards
20+
uint256 public winningChainId;
21+
address constant L2_MESSAGE_VERIFICATION_ADDRESS = 0x0000000000000000000000000000000000010009;
22+
23+
IMessageVerification public l2MessageVerifier = IMessageVerification(L2_MESSAGE_VERIFICATION_ADDRESS);
24+
25+
constructor(
26+
uint256[] memory _chainIds,
27+
address[] memory _stakingContracts
28+
) ERC20('InteropToken', 'INTR') Ownable(msg.sender) {
29+
require(_chainIds.length == _stakingContracts.length, 'Length mismatch');
30+
for (uint256 i = 0; i < _chainIds.length; ++i) {
31+
require(_stakingContracts[i] != address(0), 'Zero address used');
32+
require(approvedStakingContracts[_chainIds[i]] == address(0), 'Staking contract address already set');
33+
approvedStakingContracts[_chainIds[i]] = _stakingContracts[i];
34+
}
35+
}
36+
37+
function mint(
38+
uint256 _sourceChainId,
39+
uint256 _l1BatchNumber,
40+
uint256 _l2MessageIndex,
41+
L2Message calldata _l2MessageData,
42+
bytes32[] calldata _proof
43+
) external {
44+
// token can only be minted once per address
45+
require(addressesThatMinted[msg.sender] == false, 'Token already minted');
46+
require(approvedStakingContracts[_sourceChainId] == _l2MessageData.sender, 'Message origin not approved');
47+
bool result = checkMessageProof(_sourceChainId, _l1BatchNumber, _l2MessageIndex, _l2MessageData, _proof);
48+
require(result == true, 'Message not verified');
49+
address sender = abi.decode(_l2MessageData.data, (address));
50+
require(sender == msg.sender, 'Message sender is not depositor');
51+
rewardsByChain[_sourceChainId]++;
52+
if (rewardsByChain[_sourceChainId] > rewardsByChain[winningChainId]) {
53+
winningChainId = _sourceChainId;
54+
}
55+
addressesThatMinted[msg.sender] = true;
56+
_mint(msg.sender, 1);
57+
}
58+
59+
function checkMessageProof(
60+
uint256 _sourceChainId,
61+
uint256 _l1BatchNumber,
62+
uint256 _l2MessageIndex,
63+
L2Message calldata _l2MessageData,
64+
bytes32[] calldata _proof
65+
) public view returns (bool) {
66+
bool result = l2MessageVerifier.proveL2MessageInclusionShared(
67+
_sourceChainId,
68+
_l1BatchNumber,
69+
_l2MessageIndex,
70+
_l2MessageData,
71+
_proof
72+
);
73+
return result;
74+
}
75+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//SPDX-License-Identifier: Unlicense
2+
pragma solidity ^0.8.21;
3+
import { IL1Messenger } from '@matterlabs/zksync-contracts/contracts/system-contracts/interfaces/IL1Messenger.sol';
4+
5+
contract Staking {
6+
address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = 0x0000000000000000000000000000000000008008;
7+
IL1Messenger public L1Messenger = IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR);
8+
9+
struct Deposit {
10+
uint256 amount;
11+
bool madeFirstDeposit;
12+
}
13+
14+
// mapping to address to deposit amount and timestamp
15+
mapping(address => Deposit) public deposits;
16+
17+
function deposit() external payable {
18+
require(msg.value > 0, 'no amount deposited');
19+
Deposit memory lastDeposit = deposits[msg.sender];
20+
deposits[msg.sender].amount = lastDeposit.amount + msg.value;
21+
if (lastDeposit.madeFirstDeposit != true) {
22+
deposits[msg.sender].madeFirstDeposit = true;
23+
bytes memory message = abi.encode(msg.sender);
24+
L1Messenger.sendToL1(message);
25+
}
26+
}
27+
28+
function withdraw() external {
29+
Deposit memory lastDeposit = deposits[msg.sender];
30+
require(lastDeposit.amount > 0, 'no amount deposited');
31+
deposits[msg.sender].amount = 0;
32+
(bool sent, ) = msg.sender.call{ value: lastDeposit.amount }('');
33+
require(sent, 'send failed');
34+
}
35+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { HardhatUserConfig } from 'hardhat/config';
2+
import hardhatToolboxMochaEthersPlugin from '@nomicfoundation/hardhat-toolbox-mocha-ethers';
3+
import hardhatKeystore from '@nomicfoundation/hardhat-keystore';
4+
import { configVariable } from 'hardhat/config';
5+
6+
const config: HardhatUserConfig = {
7+
plugins: [hardhatToolboxMochaEthersPlugin, hardhatKeystore],
8+
solidity: {
9+
profiles: {
10+
default: {
11+
version: '0.8.28',
12+
},
13+
production: {
14+
version: '0.8.28',
15+
settings: {
16+
optimizer: {
17+
enabled: true,
18+
runs: 200,
19+
},
20+
},
21+
},
22+
},
23+
},
24+
ignition: {
25+
requiredConfirmations: 1,
26+
},
27+
networks: {
28+
hardhatMainnet: {
29+
type: 'edr-simulated',
30+
chainType: 'l1',
31+
},
32+
zksyncGateway: {
33+
type: 'http',
34+
url: 'http://localhost:3150',
35+
chainType: 'generic',
36+
},
37+
stakingChain1: {
38+
type: 'http',
39+
url: 'http://localhost:3050',
40+
chainType: 'generic',
41+
accounts: [configVariable('WALLET_PRIVATE_KEY')],
42+
},
43+
stakingChain2: {
44+
type: 'http',
45+
url: 'http://localhost:3250',
46+
chainType: 'generic',
47+
accounts: [configVariable('WALLET_PRIVATE_KEY')],
48+
},
49+
rewardsChain: {
50+
type: 'http',
51+
url: 'http://localhost:3350',
52+
chainType: 'generic',
53+
accounts: [configVariable('WALLET_PRIVATE_KEY')],
54+
},
55+
},
56+
};
57+
58+
export default config;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';
2+
3+
const STAKING_CHAIN_1_CONTRACT_ADDRESS = '0x...';
4+
const STAKING_CHAIN_2_CONTRACT_ADDRESS = '0x...';
5+
6+
// make sure chain ids match the chains you created in the ecosystem setup
7+
const STAKING_CHAIN_1_ID = '34234';
8+
const STAKING_CHAIN_2_ID = '5328';
9+
10+
export default buildModule('InteropTokenModule', (m) => {
11+
const approvedChainIds = [STAKING_CHAIN_1_ID, STAKING_CHAIN_2_ID];
12+
const approvedStakingContracts = [STAKING_CHAIN_1_CONTRACT_ADDRESS, STAKING_CHAIN_2_CONTRACT_ADDRESS];
13+
console.log('Approved staking contracts:', approvedStakingContracts);
14+
const counter = m.contract('InteropToken', [approvedChainIds, approvedStakingContracts]);
15+
16+
return { counter };
17+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';
2+
3+
export default buildModule('StakingModule', (m) => {
4+
const counter = m.contract('Staking');
5+
6+
return { counter };
7+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "contracts",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"compile": "hardhat compile",
7+
"deploy:staking": "hardhat ignition deploy ignition/modules/Staking.ts",
8+
"deploy:token": "hardhat ignition deploy ignition/modules/InteropToken.ts --network rewardsChain",
9+
"interop": "hardhat run ./scripts/interop-test.ts"
10+
},
11+
"devDependencies": {
12+
"@matterlabs/zksync-contracts": "^29.0.0",
13+
"@nomicfoundation/hardhat-ignition": "^3.0.0",
14+
"@nomicfoundation/hardhat-keystore": "^3.0.1",
15+
"@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.0",
16+
"@nomicfoundation/hardhat-toolbox-viem": "^5.0.0",
17+
"@openzeppelin/contracts": "^5.4.0",
18+
"@types/chai": "^4.3.20",
19+
"@types/chai-as-promised": "^8.0.2",
20+
"@types/mocha": "^10.0.10",
21+
"@types/node": "^22.18.0",
22+
"chai": "^5.3.3",
23+
"ethers": "^6.15.0",
24+
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
25+
"hardhat": "^3.0.1",
26+
"mocha": "^11.7.1",
27+
"typescript": "~5.8.0",
28+
"zksync-ethers": "^6.21.0"
29+
},
30+
"dependencies": {
31+
"dotenv": "^17.2.1"
32+
}
33+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
2+
{
3+
"compilerOptions": {
4+
"lib": ["es2023"],
5+
"module": "node16",
6+
"target": "es2022",
7+
"strict": true,
8+
"esModuleInterop": true,
9+
"skipLibCheck": true,
10+
"moduleResolution": "node16",
11+
"outDir": "dist"
12+
}
13+
}

0 commit comments

Comments
 (0)