Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Run slither and coverage #138

Merged
merged 5 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ tsconfig.tsbuildinfo
scripts/slither-results/*
!scripts/slither-results/.gitkeep



**/coverage/*
**/coverage.json
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ yarn lint
yarn lint:fix
```

## Coverage
To check the test coverage run the follow command on the desire package

```bash
npx hardhat coverage
```

## Static test
We run slither on our packages. If you want to run it should install slither

```bash
brew install slither-analyzer
```
and execute it

```bash
slither . --filter-paths "contracts/test/|node_modules/" --exclude naming-convention
```

## Contributing

We welcome (and appreciate) everyone's contributions. If you wanna contribute, read [CONTRIBUTING.md](CONTRIBUTING.md) for next steps.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@ pragma solidity 0.8.7;
import "@zetachain/protocol-contracts/contracts/evm/interfaces/ZetaInterfaces.sol";

contract ZetaConnectorMockValue is ZetaConnector {
function send(ZetaInterfaces.SendInput calldata input) external override {}
event Send(
uint256 destinationChainId,
bytes destinationAddress,
uint256 destinationGasLimit,
bytes message,
uint256 zetaValueAndGas,
bytes zetaParams
);

function send(ZetaInterfaces.SendInput calldata input) external override {
emit Send(
input.destinationChainId,
input.destinationAddress,
input.destinationGasLimit,
input.message,
input.zetaValueAndGas,
input.zetaParams
);
}

function onRevert(
address zetaTxSenderAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ contract ZetaEthMock is ERC20("Zeta", "ZETA") {
constructor(address creator, uint256 initialSupply) {
_mint(creator, initialSupply * (10 ** uint256(decimals())));
}

function deposit() external payable {
_mint(_msgSender(), msg.value);
}
}
41 changes: 35 additions & 6 deletions packages/zeta-app-contracts/test/MultiChainValue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { ZetaEth } from "@zetachain/protocol-contracts/dist/typechain-types";
import { expect } from "chai";
import { parseEther } from "ethers/lib/utils";
import { defaultAbiCoder, parseEther } from "ethers/lib/utils";
import { ethers } from "hardhat";

import {
Expand Down Expand Up @@ -72,16 +72,45 @@ describe("MultiChainValue tests", () => {
});

describe("send", () => {
it("Should send msg", async () => {
await zetaEthMockContract.approve(multiChainValueContractA.address, parseEther("1000"));
const tx = multiChainValueContractA.send(chainBId, account1Address, 10);

await expect(tx)
.to.be.emit(zetaConnectorMockContract, "Send")
.withArgs(
chainBId,
account1Address.toLowerCase(),
300000,
defaultAbiCoder.encode(["address"], [deployer.address]),
10,
defaultAbiCoder.encode(["string"], [""])
);
});

it("Should send native token", async () => {
const tx = multiChainValueContractA.sendZeta(chainBId, account1Address, { value: 10 });
await expect(tx)
.to.be.emit(zetaConnectorMockContract, "Send")
.withArgs(
chainBId,
account1Address.toLowerCase(),
300000,
defaultAbiCoder.encode(["address"], [deployer.address]),
10,
defaultAbiCoder.encode(["string"], [""])
);
});

it("Should prevent sending value to a disabled chainId", async () => {
await expect(multiChainValueContractA.send(1, account1Address, 100_000)).to.be.revertedWith(
"InvalidDestinationChainId"
);
const tx = multiChainValueContractA.send(1, account1Address, 100_000);
await expect(tx).to.be.revertedWith("InvalidDestinationChainId");
});

it("Should prevent sending 0 value", async () => {
await (await multiChainValueContractA.addAvailableChainId(1)).wait();

await expect(multiChainValueContractA.send(1, account1Address, 0)).to.be.revertedWith("InvalidZetaValueAndGas");
const tx = multiChainValueContractA.send(1, account1Address, 0);
await expect(tx).to.be.revertedWith("InvalidZetaValueAndGas");
});

it("Should prevent sending if the account has no Zeta balance", async () => {
Expand Down
23 changes: 20 additions & 3 deletions packages/zevm-app-contracts/contracts/disperse/Disperse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ pragma solidity 0.8.7;
import "@openzeppelin/contracts/interfaces/IERC20.sol";

contract Disperse {
function disperseEther(address[] calldata recipients, uint256[] calldata values) external payable {
bool private locked;

modifier noReentrancy() {
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}

function disperseEther(address[] calldata recipients, uint256[] calldata values) external payable noReentrancy {
require(recipients.length == values.length, "Recipients and values length mismatch");

for (uint256 i = 0; i < recipients.length; i++) {
Expand All @@ -19,14 +28,22 @@ contract Disperse {
}
}

function disperseToken(IERC20 token, address[] calldata recipients, uint256[] calldata values) external {
function disperseToken(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external noReentrancy {
uint256 total = 0;
for (uint256 i = 0; i < recipients.length; i++) total += values[i];
require(token.transferFrom(msg.sender, address(this), total));
for (uint256 i = 0; i < recipients.length; i++) require(token.transfer(recipients[i], values[i]));
}

function disperseTokenSimple(IERC20 token, address[] calldata recipients, uint256[] calldata values) external {
function disperseTokenSimple(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external noReentrancy {
for (uint256 i = 0; i < recipients.length; i++)
require(token.transferFrom(msg.sender, recipients[i], values[i]));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import "./Synthetixio/StakingRewards.sol";
contract RewardDistributor is StakingRewards {
uint16 internal constant MAX_DEADLINE = 200;

IERC20 public stakingTokenA;
IERC20 public stakingTokenB;
SystemContract private systemContract;
IERC20 public immutable stakingTokenA;
IERC20 public immutable stakingTokenB;
SystemContract private immutable systemContract;
uint256 public minCoolDown;
uint256 public minStakingPeriod;
mapping(address => uint256) public lastDeposit;
Expand All @@ -24,6 +24,8 @@ contract RewardDistributor is StakingRewards {
error ZeroStakeAmount();
error InvalidTokenAddress();
error MinimumStakingPeriodNotMet();
error ApproveFailed();
error TransferFailed();

event MinCoolDownUpdated(address callerAddress, uint256 minCoolDown);
event MinStakingPeriodUpdated(address callerAddress, uint256 minStakingPeriod);
Expand All @@ -43,12 +45,17 @@ contract RewardDistributor is StakingRewards {
}

function _addLiquidity(uint256 tokenAmountA, uint256 tokenAmountB) internal returns (uint256) {
stakingTokenA.transferFrom(msg.sender, address(this), tokenAmountA);
stakingTokenA.approve(systemContract.uniswapv2Router02Address(), 0);
stakingTokenA.approve(systemContract.uniswapv2Router02Address(), tokenAmountA);

stakingTokenB.transferFrom(msg.sender, address(this), tokenAmountB);
stakingTokenB.approve(systemContract.uniswapv2Router02Address(), tokenAmountB);
bool transfer = stakingTokenA.transferFrom(msg.sender, address(this), tokenAmountA);
if (!transfer) revert TransferFailed();
bool approve = stakingTokenA.approve(systemContract.uniswapv2Router02Address(), 0);
if (!approve) revert ApproveFailed();
approve = stakingTokenA.approve(systemContract.uniswapv2Router02Address(), tokenAmountA);
if (!approve) revert ApproveFailed();

transfer = stakingTokenB.transferFrom(msg.sender, address(this), tokenAmountB);
if (!transfer) revert TransferFailed();
approve = stakingTokenB.approve(systemContract.uniswapv2Router02Address(), tokenAmountB);
if (!approve) revert ApproveFailed();

(, , uint LPTokenAmount) = IUniswapV2Router02(systemContract.uniswapv2Router02Address()).addLiquidity(
address(stakingTokenA),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ contract StakingRewards is RewardsDistributionRecipient, ReentrancyGuard, Pausab

/* ========== STATE VARIABLES ========== */

IERC20 public rewardsToken;
IERC20 public stakingToken;
IERC20 public immutable rewardsToken;
IERC20 public immutable stakingToken;
uint256 public periodFinish = 0;
uint256 public rewardRate = 0;
uint256 public rewardsDuration = 7 days;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ interface SystemContractErrors {
}

contract MockSystemContract is SystemContractErrors {
error TransferFailed();

mapping(uint256 => uint256) public gasPriceByChainId;
mapping(uint256 => address) public gasCoinZRC20ByChainId;
mapping(uint256 => address) public gasZetaPoolByChainId;

address public wZetaContractAddress;
address public uniswapv2FactoryAddress;
address public uniswapv2Router02Address;
address public immutable uniswapv2FactoryAddress;
address public immutable uniswapv2Router02Address;

event SystemContractDeployed();
event SetGasPrice(uint256, uint256);
Expand Down Expand Up @@ -79,7 +81,8 @@ contract MockSystemContract is SystemContractErrors {

function onCrossChainCall(address target, address zrc20, uint256 amount, bytes calldata message) external {
zContext memory context = zContext({sender: msg.sender, origin: "", chainID: block.chainid});
IZRC20(zrc20).transfer(target, amount);
bool transfer = IZRC20(zrc20).transfer(target, amount);
if (!transfer) revert TransferFailed();
zContract(target).onCrossChainCall(context, zrc20, amount, message);
}
}
99 changes: 99 additions & 0 deletions packages/zevm-app-contracts/test/Disperse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { parseUnits } from "@ethersproject/units";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers, network } from "hardhat";

import { Disperse, Disperse__factory, MockZRC20, MockZRC20__factory } from "../typechain-types";

describe("Disperse tests", () => {
let disperseContract: Disperse;

let accounts: SignerWithAddress[];
let deployer: SignerWithAddress;

beforeEach(async () => {
[deployer, ...accounts] = await ethers.getSigners();

await network.provider.send("hardhat_setBalance", [deployer.address, parseUnits("1000000").toHexString()]);

const DisperseFactory = (await ethers.getContractFactory("Disperse")) as Disperse__factory;
disperseContract = (await DisperseFactory.deploy()) as Disperse;
await disperseContract.deployed();
});

describe("Disperse", () => {
it("Should disperse ETH", async () => {
const amount = parseUnits("10");
const balance0 = await ethers.provider.getBalance(accounts[0].address);
const balance1 = await ethers.provider.getBalance(accounts[1].address);
await disperseContract.disperseEther([accounts[0].address, accounts[1].address], [amount, amount.mul(2)], {
value: amount.mul(3),
});

const balance0After = await ethers.provider.getBalance(accounts[0].address);
const balance1After = await ethers.provider.getBalance(accounts[1].address);

expect(balance0After.sub(balance0)).to.be.eq(amount);
expect(balance1After.sub(balance1)).to.be.eq(amount.mul(2));
});

it("Should disperse ETH with surplus", async () => {
const amount = parseUnits("10");
const balance0 = await ethers.provider.getBalance(accounts[0].address);
const balance1 = await ethers.provider.getBalance(accounts[1].address);
await disperseContract.disperseEther([accounts[0].address, accounts[1].address], [amount, amount.mul(2)], {
value: amount.mul(4),
});

const balance0After = await ethers.provider.getBalance(accounts[0].address);
const balance1After = await ethers.provider.getBalance(accounts[1].address);

expect(balance0After.sub(balance0)).to.be.eq(amount);
expect(balance1After.sub(balance1)).to.be.eq(amount.mul(2));
});

it("Should disperse token", async () => {
const MockTokenFactory = (await ethers.getContractFactory("MockZRC20")) as MockZRC20__factory;
const mockTokenContract = (await MockTokenFactory.deploy(1_000_000, "MOCK", "MOCK")) as MockZRC20;
await mockTokenContract.deployed();
await mockTokenContract.approve(disperseContract.address, parseUnits("1000000"));

const amount = parseUnits("10");
const balance0 = await mockTokenContract.balanceOf(accounts[0].address);
const balance1 = await mockTokenContract.balanceOf(accounts[1].address);
await disperseContract.disperseToken(
mockTokenContract.address,
[accounts[0].address, accounts[1].address],
[amount, amount.mul(2)]
);

const balance0After = await mockTokenContract.balanceOf(accounts[0].address);
const balance1After = await mockTokenContract.balanceOf(accounts[1].address);

expect(balance0After.sub(balance0)).to.be.eq(amount);
expect(balance1After.sub(balance1)).to.be.eq(amount.mul(2));
});

it("Should disperse token simple", async () => {
const MockTokenFactory = (await ethers.getContractFactory("MockZRC20")) as MockZRC20__factory;
const mockTokenContract = (await MockTokenFactory.deploy(1_000_000, "MOCK", "MOCK")) as MockZRC20;
await mockTokenContract.deployed();
await mockTokenContract.approve(disperseContract.address, parseUnits("1000000"));

const amount = parseUnits("10");
const balance0 = await mockTokenContract.balanceOf(accounts[0].address);
const balance1 = await mockTokenContract.balanceOf(accounts[1].address);
await disperseContract.disperseTokenSimple(
mockTokenContract.address,
[accounts[0].address, accounts[1].address],
[amount, amount.mul(2)]
);

const balance0After = await mockTokenContract.balanceOf(accounts[0].address);
const balance1After = await mockTokenContract.balanceOf(accounts[1].address);

expect(balance0After.sub(balance0)).to.be.eq(amount);
expect(balance1After.sub(balance1)).to.be.eq(amount.mul(2));
});
});
});
Loading
Loading