Skip to content

Commit

Permalink
feat: prototype localnet (#202)
Browse files Browse the repository at this point in the history
Co-authored-by: lumtis <[email protected]>
  • Loading branch information
skosito and lumtis authored Jul 4, 2024
1 parent 6bf41ee commit 41920d0
Show file tree
Hide file tree
Showing 27 changed files with 1,751 additions and 335 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.7;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract Receiver {
contract ReceiverEVM {
using SafeERC20 for IERC20;

event ReceivedPayable(address sender, uint256 value, string str, uint256 num, bool flag);
Expand Down
2 changes: 1 addition & 1 deletion contracts/prototypes/zevm/GatewayZEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract GatewayZEVM is Initializable, OwnableUpgradeable, UUPSUpgradeable {
error CallerIsNotFungibleModule();
error InvalidTarget();

event Call(address indexed sender, bytes indexed receiver, bytes message);
event Call(address indexed sender, bytes receiver, bytes message);
event Withdrawal(address indexed from, bytes to, uint256 value, uint256 gasfee, uint256 protocolFlatFee, bytes message);

/// @custom:oz-upgrades-unsafe-allow constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces.sol";
import "../../zevm/interfaces/IZRC20.sol";

contract Sender {
contract SenderZEVM {
address public gateway;
error ApprovalFailed();

Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "uniswap-v2-deploy-plugin";
import "solidity-coverage";
import "hardhat-gas-reporter";
import "./tasks/addresses";
import "./tasks/localnet";
import "@openzeppelin/hardhat-upgrades";

import { getHardhatConfigNetworks } from "@zetachain/networks";
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@zetachain/networks": "^8.0.0",
"axios": "^1.6.5",
"chai": "^4.3.6",
"concurrently": "^8.2.2",
"cpx": "^1.5.0",
"del-cli": "^5.0.0",
"dotenv": "^16.0.0",
Expand Down Expand Up @@ -58,7 +59,8 @@
"tsconfig-paths": "^3.14.1",
"typechain": "^8.1.0",
"typescript": "^4.6.3",
"uniswap-v2-deploy-plugin": "^0.0.4"
"uniswap-v2-deploy-plugin": "^0.0.4",
"wait-on": "^7.2.0"
},
"files": [
"contracts",
Expand All @@ -83,10 +85,12 @@
"lint": "npx eslint . --ext .js,.ts",
"lint:fix": "npx eslint . --ext .js,.ts,.json --fix --ignore-pattern coverage/ --ignore-pattern coverage.json",
"lint:sol": "solhint 'contracts/**/*.sol'",
"localnet": "concurrently --names \"NODE,WORKER\" --prefix-colors \"blue.bold,green.bold\" \"npx hardhat node\" \"wait-on tcp:8545 && yarn worker\"",
"prepublishOnly": "yarn build",
"test": "npx hardhat test",
"test:prototypes": "yarn compile && npx hardhat test test/prototypes/*",
"tsc:watch": "npx tsc --watch"
"tsc:watch": "npx tsc --watch",
"worker": "npx hardhat run scripts/worker.ts --network localhost"
},
"types": "./dist/lib/index.d.ts",
"version": "0.0.8"
Expand Down

Large diffs are not rendered by default.

28 changes: 10 additions & 18 deletions pkg/contracts/prototypes/zevm/gatewayzevm.sol/gatewayzevm.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions scripts/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Worker script

To start localnet execute:

```
yarn localnet
```

This will run hardhat local node and worker script, which will deploy all contracts, and listen and react to events, facilitating communication between contracts.
Tasks to interact with localnet are located in `tasks/localnet`. To make use of default contract addresses on localnet, start localnet from scratch, so contracts are deployed on same addresses. Otherwise, provide custom addresses as tasks parameters.

To only show logs from worker and hide hardhat node logs:
```
yarn localnet --hide="NODE"
```
202 changes: 202 additions & 0 deletions scripts/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { AddressZero } from "@ethersproject/constants";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { SystemContract, ZRC20 } from "@typechain-types";
import { parseEther } from "ethers/lib/utils";
import { ethers, upgrades } from "hardhat";

const hre = require("hardhat");

export const FUNGIBLE_MODULE_ADDRESS = "0x735b14BB79463307AAcBED86DAf3322B1e6226aB";

const deploySystemContracts = async (tss: SignerWithAddress) => {
// Prepare EVM
// Deploy system contracts (gateway and custody)
const GatewayEVM = await ethers.getContractFactory("GatewayEVM");
const Custody = await ethers.getContractFactory("ERC20CustodyNew");

const gatewayEVM = await upgrades.deployProxy(GatewayEVM, [tss.address], {
initializer: "initialize",
kind: "uups",
});
console.log("GatewayEVM:", gatewayEVM.address);

const custody = await Custody.deploy(gatewayEVM.address);
await gatewayEVM.setCustody(custody.address);

// Prepare ZEVM
// Deploy system contracts (gateway and system)
const SystemContractFactory = await ethers.getContractFactory("SystemContractMock");
const systemContract = (await SystemContractFactory.deploy(AddressZero, AddressZero, AddressZero)) as SystemContract;

const GatewayZEVM = await ethers.getContractFactory("GatewayZEVM");
const gatewayZEVM = await upgrades.deployProxy(GatewayZEVM, [], {
initializer: "initialize",
kind: "uups",
});
console.log("GatewayZEVM:", gatewayZEVM.address);

return {
custody,
gatewayEVM,
gatewayZEVM,
systemContract,
};
};

const deployTestContracts = async (
systemContracts,
ownerEVM: SignerWithAddress,
ownerZEVM: SignerWithAddress,
fungibleModuleSigner: SignerWithAddress
) => {
// Prepare EVM
// Deploy test contracts (erc20, receiver) and mint funds to test accounts
const TestERC20 = await ethers.getContractFactory("TestERC20");
const ReceiverEVM = await ethers.getContractFactory("ReceiverEVM");

const token = await TestERC20.deploy("Test Token", "TTK");
const receiverEVM = await ReceiverEVM.deploy();
await token.mint(ownerEVM.address, ethers.utils.parseEther("1000"));

// Transfer some tokens to the custody contract
await token.transfer(systemContracts.custody.address, ethers.utils.parseEther("500"));

// Prepare ZEVM
// Deploy test contracts (test zContract, zrc20, sender) and mint funds to test accounts
const TestZContract = await ethers.getContractFactory("TestZContract");
const testZContract = await TestZContract.deploy();

const ZRC20Factory = await ethers.getContractFactory("ZRC20New");
const ZRC20Contract = (await ZRC20Factory.connect(fungibleModuleSigner).deploy(
"TOKEN",
"TKN",
18,
1,
1,
0,
systemContracts.systemContract.address,
systemContracts.gatewayZEVM.address
)) as ZRC20;

await systemContracts.systemContract.setGasCoinZRC20(1, ZRC20Contract.address);
await systemContracts.systemContract.setGasPrice(1, ZRC20Contract.address);
await ZRC20Contract.connect(fungibleModuleSigner).deposit(ownerZEVM.address, parseEther("100"));
await ZRC20Contract.connect(ownerZEVM).approve(systemContracts.gatewayZEVM.address, parseEther("100"));

// Include abi of gatewayZEVM events, so hardhat can decode them automatically
const senderArtifact = await hre.artifacts.readArtifact("SenderZEVM");
const gatewayZEVMArtifact = await hre.artifacts.readArtifact("GatewayZEVM");
const senderABI = [
...senderArtifact.abi,
...gatewayZEVMArtifact.abi.filter((f: ethers.utils.Fragment) => f.type === "event"),
];

const SenderZEVM = new ethers.ContractFactory(senderABI, senderArtifact.bytecode, ownerZEVM);
const senderZEVM = await SenderZEVM.deploy(systemContracts.gatewayZEVM.address);
await ZRC20Contract.connect(fungibleModuleSigner).deposit(senderZEVM.address, parseEther("100"));

return {
ZRC20Contract,
receiverEVM,
senderZEVM,
testZContract,
};
};

export const startWorker = async () => {
const [ownerEVM, ownerZEVM, tss] = await ethers.getSigners();

// Impersonate the fungible module account
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [FUNGIBLE_MODULE_ADDRESS],
});

// Get a signer for the fungible module account
const fungibleModuleSigner = await ethers.getSigner(FUNGIBLE_MODULE_ADDRESS);
hre.network.provider.send("hardhat_setBalance", [FUNGIBLE_MODULE_ADDRESS, parseEther("1000000").toHexString()]);

// Deploy system and test contracts
const systemContracts = await deploySystemContracts(tss);
const testContracts = await deployTestContracts(systemContracts, ownerEVM, ownerZEVM, fungibleModuleSigner);

// Listen to contracts events
// event Call(address indexed sender, bytes receiver, bytes message);
systemContracts.gatewayZEVM.on("Call", async (...args: Array<any>) => {
console.log("Worker: Call event on GatewayZEVM.");
console.log("Worker: Calling ReceiverEVM through GatewayEVM...");
const receiver = args[1];
const message = args[2];
const executeTx = await systemContracts.gatewayEVM.execute(receiver, message, { value: 0 });
await executeTx.wait();
});

// event Withdrawal(address indexed from, bytes to, uint256 value, uint256 gasfee, uint256 protocolFlatFee, bytes message);
systemContracts.gatewayZEVM.on("Withdrawal", async (...args: Array<any>) => {
console.log("Worker: Withdrawal event on GatewayZEVM.");
const receiver = args[1];
const message = args[5];
if (message != "0x") {
console.log("Worker: Calling ReceiverEVM through GatewayEVM...");
const executeTx = await systemContracts.gatewayEVM.execute(receiver, message, { value: 0 });
await executeTx.wait();
}
});

testContracts.receiverEVM.on("ReceivedPayable", () => {
console.log("ReceiverEVM: receivePayable called!");
});

// event Call(address indexed sender, address indexed receiver, bytes payload);
systemContracts.gatewayEVM.on("Call", async (...args: Array<any>) => {
console.log("Worker: Call event on GatewayEVM.");
console.log("Worker: Calling TestZContract through GatewayZEVM...");
const zContract = args[1];
const payload = args[2];
const executeTx = await systemContracts.gatewayZEVM.connect(fungibleModuleSigner).execute(
[systemContracts.gatewayZEVM.address, fungibleModuleSigner.address, 1],
// onCrosschainCall contains zrc20 and amount which is not available in Call event
testContracts.ZRC20Contract.address,
parseEther("0"),
zContract,
payload
);
await executeTx.wait();
});

// event Deposit(address indexed sender, address indexed receiver, uint256 amount, address asset, bytes payload);
systemContracts.gatewayEVM.on("Deposit", async (...args: Array<any>) => {
console.log("Worker: Deposit event on GatewayEVM.");
const receiver = args[1];
const asset = args[3];
const payload = args[4];
if (payload != "0x") {
console.log("Worker: Calling TestZContract through GatewayZEVM...");
const executeTx = await systemContracts.gatewayZEVM
.connect(fungibleModuleSigner)
.execute(
[systemContracts.gatewayZEVM.address, fungibleModuleSigner.address, 1],
asset,
parseEther("0"),
receiver,
payload
);
await executeTx.wait();
}
});

testContracts.testZContract.on("ContextData", async () => {
console.log("TestZContract: onCrosschainCall called!");
});

process.stdin.resume();
};

startWorker()
.then(() => {
console.log("Setup complete, monitoring events. Press CTRL+C to exit.");
})
.catch((error) => {
console.error("Failed to deploy contracts or set up listeners:", error);
process.exit(1);
});
104 changes: 104 additions & 0 deletions tasks/localnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { task } from "hardhat/config";

declare const hre: any;

// Contains tasks to make it easier to interact with prototype contracts localnet.
// To make use of default contract addresses on localnet, start localnet from scratch, so contracts are deployed on same addresses.
// Otherwise, provide custom addresses as parameters.

task("zevm-call", "calls evm contract from zevm account")
.addOptionalParam("gatewayZEVM", "contract address of gateway on ZEVM", "0x413b1AfCa96a3df5A686d8BFBF93d30688a7f7D9")
.addOptionalParam("receiverEVM", "contract address of receiver on EVM", "0x821f3361D454cc98b7555221A06Be563a7E2E0A6")
.setAction(async (taskArgs) => {
const gatewayZEVM = await hre.ethers.getContractAt("GatewayZEVM", taskArgs.gatewayZEVM);
const receiverEVM = await hre.ethers.getContractAt("ReceiverEVM", taskArgs.receiverEVM);

const str = "Hello!";
const num = 42;
const flag = true;

// Encode the function call data and call on zevm
const message = receiverEVM.interface.encodeFunctionData("receivePayable", [str, num, flag]);
try {
const callTx = await gatewayZEVM.call(receiverEVM.address, message);
await callTx.wait();
console.log("ReceiverEVM called from ZEVM");
} catch (e) {
console.error("Error calling ReceiverEVM:", e);
}
});

task("zevm-withdraw-and-call", "withdraws zrc20 and calls evm contract from zevm account")
.addOptionalParam("gatewayZEVM", "contract address of gateway on ZEVM", "0x413b1AfCa96a3df5A686d8BFBF93d30688a7f7D9")
.addOptionalParam("receiverEVM", "contract address of receiver on EVM", "0x821f3361D454cc98b7555221A06Be563a7E2E0A6")
.addOptionalParam("zrc20", "contract address of zrc20", "0x9fd96203f7b22bCF72d9DCb40ff98302376cE09c")
.addOptionalParam("amount", "amount to withdraw", "1")
.setAction(async (taskArgs) => {
const gatewayZEVM = await hre.ethers.getContractAt("GatewayZEVM", taskArgs.gatewayZEVM);
const receiverEVM = await hre.ethers.getContractAt("ReceiverEVM", taskArgs.receiverEVM);
const zrc20 = await hre.ethers.getContractAt("ZRC20New", taskArgs.zrc20);
const [, ownerZEVM] = await hre.ethers.getSigners();

const str = "Hello!";
const num = 42;
const flag = true;

// Encode the function call data and call on zevm
const message = receiverEVM.interface.encodeFunctionData("receivePayable", [str, num, flag]);

try {
const callTx = await gatewayZEVM
.connect(ownerZEVM)
.withdrawAndCall(receiverEVM.address, hre.ethers.utils.parseEther(taskArgs.amount), zrc20.address, message);
await callTx.wait();
console.log("ReceiverEVM called from ZEVM");
} catch (e) {
console.error("Error calling ReciverEVM:", e);
}
});

task("evm-call", "calls zevm zcontract from evm account")
.addOptionalParam("gatewayEVM", "contract address of gateway on EVM", "0xB06c856C8eaBd1d8321b687E188204C1018BC4E5")
.addOptionalParam("zContract", "contract address of zContract on ZEVM", "0x71089Ba41e478702e1904692385Be3972B2cBf9e")
.setAction(async (taskArgs) => {
const gatewayEVM = await hre.ethers.getContractAt("GatewayEVM", taskArgs.gatewayEVM);
const zContract = await hre.ethers.getContractAt("TestZContract", taskArgs.zContract);

const message = hre.ethers.utils.defaultAbiCoder.encode(["string"], ["hello"]);

try {
const callTx = await gatewayEVM.call(zContract.address, message);
await callTx.wait();
console.log("TestZContract called from EVM");
} catch (e) {
console.error("Error calling TestZContract:", e);
}
});

task("evm-deposit-and-call", "deposits erc20 and calls zevm zcontract from evm account")
.addOptionalParam("gatewayEVM", "contract address of gateway on EVM", "0xB06c856C8eaBd1d8321b687E188204C1018BC4E5")
.addOptionalParam("zContract", "contract address of zContract on ZEVM", "0x71089Ba41e478702e1904692385Be3972B2cBf9e")
.addOptionalParam("erc20", "contract address of erc20", "0x02df3a3F960393F5B349E40A599FEda91a7cc1A7")
.addOptionalParam("amount", "amount to deposit", "1")
.setAction(async (taskArgs) => {
const gatewayEVM = await hre.ethers.getContractAt("GatewayEVM", taskArgs.gatewayEVM);
const zContract = await hre.ethers.getContractAt("TestZContract", taskArgs.zContract);
const erc20 = await hre.ethers.getContractAt("TestERC20", taskArgs.erc20);

await erc20.approve(gatewayEVM.address, hre.ethers.utils.parseEther(taskArgs.amount));

const payload = hre.ethers.utils.defaultAbiCoder.encode(["string"], ["hello"]);

try {
const callTx = await gatewayEVM["depositAndCall(address,uint256,address,bytes)"](
zContract.address,
hre.ethers.utils.parseEther(taskArgs.amount),
erc20.address,
payload
);
await callTx.wait();
console.log("TestZContract called from EVM");
} catch (e) {
console.error("Error calling TestZContract:", e);
}
});
Loading

0 comments on commit 41920d0

Please sign in to comment.