Skip to content

Commit

Permalink
Added some foundry tests for dst chain
Browse files Browse the repository at this point in the history
  • Loading branch information
byshape committed Jan 2, 2024
1 parent ac55906 commit 067d158
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ build
.coverage_artifacts
.idea
.env

# foundry
cache_forge
out
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
12 changes: 7 additions & 5 deletions contracts/EscrowFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.s
import { ClonesWithImmutableArgs } from "clones-with-immutable-args/ClonesWithImmutableArgs.sol";

import { IEscrowFactory } from "./interfaces/IEscrowFactory.sol";
import { Escrow } from "./Escrow.sol";

contract EscrowFactory is IEscrowFactory {
using AddressLib for Address;
Expand Down Expand Up @@ -67,6 +66,7 @@ contract EscrowFactory is IEscrowFactory {
* @dev Creates a new escrow contract for taker.
*/
function createEscrow(DstEscrowImmutablesCreation calldata dstEscrowImmutables) external {
// Check that the escrow cancellation will start not later than the cancellation time on the source chain.
if (
block.timestamp +
dstEscrowImmutables.timelocks.finality +
Expand All @@ -88,9 +88,9 @@ contract EscrowFactory is IEscrowFactory {
dstEscrowImmutables.timelocks.publicUnlock
);
bytes32 salt = keccak256(abi.encodePacked(data, msg.sender));
Escrow escrow = _createEscrow(data, salt);
address escrow = _createEscrow(data, salt);
IERC20(dstEscrowImmutables.token).safeTransferFrom(
msg.sender, address(escrow), dstEscrowImmutables.amount + dstEscrowImmutables.safetyDeposit
msg.sender, escrow, dstEscrowImmutables.amount + dstEscrowImmutables.safetyDeposit
);
}

Expand All @@ -101,7 +101,9 @@ contract EscrowFactory is IEscrowFactory {
function _createEscrow(
bytes memory data,
bytes32 salt
) private returns (Escrow clone) {
clone = Escrow(IMPLEMENTATION.clone3(data, salt));
) private returns (address clone) {
clone = address(
uint160(IMPLEMENTATION.clone3(data, salt)) & 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
);
}
}
6 changes: 6 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = 'contracts'
out = 'out'
libs = ['node_modules', 'lib']
test = 'test'
cache_path = 'cache_forge'
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"lint:js:fix": "eslint . --fix",
"lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"",
"lint:sol:fix": "solhint --max-warnings 0 \"contracts/**/*.sol\" --fix",
"lint:foundry": "solhint --max-warnings 0 \"test/**/*.sol\"",
"lint:foundry:fix": "solhint --max-warnings 0 \"test/**/*.sol\" --fix",
"test": "hardhat test --parallel",
"test:ci": "hardhat test"
}
Expand Down
11 changes: 11 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@1inch/=node_modules/@1inch/
@chainlink/=node_modules/@chainlink/
@eth-optimism/=node_modules/@eth-optimism/
@openzeppelin/=node_modules/@openzeppelin/
clones-with-immutable-args/=node_modules/clones-with-immutable-args/
ds-test/=lib/forge-std/lib/ds-test/src/
eth-gas-reporter/=node_modules/eth-gas-reporter/
forge-std/=lib/forge-std/src/
hardhat-deploy/=node_modules/hardhat-deploy/
hardhat/=node_modules/hardhat/
solidity-utils/=node_modules/@1inch/solidity-utils/
5 changes: 5 additions & 0 deletions test/EscrowFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ describe('EscrowFactory', async function () {
}
});

// Migrated
it('should deploy clones for taker', async function () {
for (let i = 0; i < 3; i++) {
const { accounts, tokens, dstClone, tx, escrowImmutables } = await deployCloneDst();
Expand All @@ -142,6 +143,7 @@ describe('EscrowFactory', async function () {
}
});

// Migrated
it('should not deploy clone for taker when it is unsafe', async function () {
const { contracts, tx, escrowImmutables } = await deployCloneDst();
await time.setNextBlockTimestamp(escrowImmutables.srcCancellationTimestamp + 1n);
Expand All @@ -156,6 +158,7 @@ describe('EscrowFactory', async function () {
).to.be.revertedWithCustomError(srcClone, 'InvalidWithdrawalTime');
});

// Migrated
it('should not withdraw tokens on the destination chain during finality lock', async function () {
const { accounts, tokens, dstClone, tx, escrowImmutables, secret } = await deployCloneDst();
await expect(tx).to.changeTokenBalances(
Expand Down Expand Up @@ -195,6 +198,7 @@ describe('EscrowFactory', async function () {
).to.be.revertedWithCustomError(srcClone, 'InvalidSecret');
});

// Migrated
it('should withdraw tokens on the destination chain by resolver', async function () {
const { accounts, tokens, dstClone, tx, escrowImmutables, secret } = await deployCloneDst();
await expect(tx).to.changeTokenBalances(
Expand All @@ -220,6 +224,7 @@ describe('EscrowFactory', async function () {
);
});

// Migrated
it('should not withdraw tokens on the destination chain with the wrong secret', async function () {
const { accounts, tokens, dstClone, tx, escrowImmutables } = await deployCloneDst();
await expect(tx).to.changeTokenBalances(
Expand Down
73 changes: 73 additions & 0 deletions test/foundry/Escrow.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import { Escrow, IEscrow } from "../../contracts/Escrow.sol";
import { IEscrowFactory } from "../../contracts/EscrowFactory.sol";

import { BaseSetup } from "../utils/BaseSetup.sol";

contract EscrowTest is BaseSetup {
function setUp() public virtual override {
BaseSetup.setUp();
}

/* solhint-disable func-name-mixedcase */

function test_NoWithdrawalDuringFinalityLockDst() public {
(
IEscrowFactory.DstEscrowImmutablesCreation memory immutables,
Escrow dstClone
) = _prepareDataDst(SECRET, TAKING_AMOUNT);

// deploy escrow
vm.startPrank(bob);
escrowFactory.createEscrow(immutables);

// withdraw
vm.expectRevert(IEscrow.InvalidWithdrawalTime.selector);
dstClone.withdrawDst(SECRET);
}

function test_WithdrawByResolverDst() public {
(
IEscrowFactory.DstEscrowImmutablesCreation memory immutables,
Escrow dstClone
) = _prepareDataDst(SECRET, TAKING_AMOUNT);

// deploy escrow
vm.startPrank(bob);
escrowFactory.createEscrow(immutables);

uint256 balanceAlice = dai.balanceOf(alice);
uint256 balanceBob = dai.balanceOf(bob);
uint256 balanceEscrow = dai.balanceOf(address(dstClone));

// withdraw
vm.warp(block.timestamp + dstTimelocks.finality + 10);
dstClone.withdrawDst(SECRET);

assertEq(dai.balanceOf(alice), balanceAlice + TAKING_AMOUNT);
assertEq(dai.balanceOf(bob), balanceBob + SAFETY_DEPOSIT);
assertEq(dai.balanceOf(address(dstClone)), balanceEscrow - (TAKING_AMOUNT + SAFETY_DEPOSIT));
}

function test_NoWithdrawalWithWrongSecretDst() public {
bytes32 wrongSecret = keccak256(abi.encodePacked("wrong secret"));

(
IEscrowFactory.DstEscrowImmutablesCreation memory immutables,
Escrow dstClone
) = _prepareDataDst(SECRET, TAKING_AMOUNT);

// deploy escrow
vm.startPrank(bob);
escrowFactory.createEscrow(immutables);

// withdraw
vm.warp(block.timestamp + dstTimelocks.finality + 100);
vm.expectRevert(IEscrow.InvalidSecret.selector);
dstClone.withdrawDst(wrongSecret);
}

/* solhint-enable func-name-mixedcase */
}
50 changes: 50 additions & 0 deletions test/foundry/EscrowFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import { Escrow, IEscrow } from "../../contracts/Escrow.sol";
import { IEscrowFactory } from "../../contracts/EscrowFactory.sol";

import { BaseSetup } from "../utils/BaseSetup.sol";

contract EscrowFactoryTest is BaseSetup {
function setUp() public virtual override {
BaseSetup.setUp();
}

/* solhint-disable func-name-mixedcase */

function testFuzz_DeployCloneForTaker(bytes32 secret, uint256 amount) public {
vm.assume(amount > 0.1 ether && amount < 1 ether);
(
IEscrowFactory.DstEscrowImmutablesCreation memory immutables,
Escrow dstClone
) = _prepareDataDst(secret, amount);
uint256 balanceBob = dai.balanceOf(bob);
uint256 balanceEscrow = dai.balanceOf(address(dstClone));

// deploy escrow
vm.prank(bob);
escrowFactory.createEscrow(immutables);

assertEq(dai.balanceOf(bob), balanceBob - (amount + immutables.safetyDeposit));
assertEq(dai.balanceOf(address(dstClone)), balanceEscrow + amount + immutables.safetyDeposit);

IEscrow.DstEscrowImmutables memory returnedImmutables = dstClone.dstEscrowImmutables();
assertEq(returnedImmutables.hashlock, uint256(keccak256(abi.encodePacked(secret))));
assertEq(returnedImmutables.amount, amount);
}

function test_NoUnsafeDeploymentForTaker() public {

(IEscrowFactory.DstEscrowImmutablesCreation memory immutables,) = _prepareDataDst(SECRET, TAKING_AMOUNT);

vm.warp(immutables.srcCancellationTimestamp + 1);

// deploy escrow
vm.prank(bob);
vm.expectRevert(IEscrowFactory.InvalidCreationTime.selector);
escrowFactory.createEscrow(immutables);
}

/* solhint-enable func-name-mixedcase */
}
125 changes: 125 additions & 0 deletions test/utils/BaseSetup.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import { Test } from "forge-std/Test.sol";

import { TokenCustomDecimalsMock } from "solidity-utils/contracts/mocks/TokenCustomDecimalsMock.sol";
import { TokenMock } from "solidity-utils/contracts/mocks/TokenMock.sol";

import { Escrow, IEscrow } from "../../contracts/Escrow.sol";
import { EscrowFactory, IEscrowFactory } from "../../contracts/EscrowFactory.sol";

import { Utils } from "./Utils.sol";

contract BaseSetup is Test {
/* solhint-disable private-vars-leading-underscore */
bytes32 internal constant SECRET = keccak256(abi.encodePacked("secret"));
uint256 internal constant TAKING_AMOUNT = 0.5 ether;
uint256 internal constant SAFETY_DEPOSIT = 0.05 ether;

Utils internal utils;
address payable[] internal users;

address internal alice;
address internal bob;

TokenMock internal dai;
TokenCustomDecimalsMock internal usdc;

address internal limitOrderProtocol;
EscrowFactory internal escrowFactory;
Escrow internal escrow;

IEscrow.SrcTimelocks internal srcTimelocks = IEscrow.SrcTimelocks({
finality: 120,
publicUnlock: 900
});
IEscrow.DstTimelocks internal dstTimelocks = IEscrow.DstTimelocks({
finality: 300,
unlock: 240,
publicUnlock: 360
});
/* solhint-enable private-vars-leading-underscore */

function setUp() public virtual {
utils = new Utils();
users = utils.createUsers(2);

alice = users[0];
vm.label(alice, "Alice");
bob = users[1];
vm.label(bob, "Bob");

_deployTokens();
dai.mint(bob, 1000 ether);
usdc.mint(alice, 1000 ether);

_deployContracts();

vm.prank(bob);
dai.approve(address(escrowFactory), 1000 ether);
vm.prank(alice);
usdc.approve(address(escrowFactory), 1000 ether);
}

function _deployTokens() internal {
dai = new TokenMock("DAI", "DAI");
vm.label(address(dai), "DAI");
usdc = new TokenCustomDecimalsMock("USDC", "USDC", 1000 ether, 6);
vm.label(address(usdc), "USDC");
}

function _deployContracts() internal {
limitOrderProtocol = address(this);
escrow = new Escrow();
vm.label(address(escrow), "Escrow");
escrowFactory = new EscrowFactory(address(escrow), limitOrderProtocol);
vm.label(address(escrowFactory), "EscrowFactory");
}

function _prepareDataDst(bytes32 secret, uint256 amount) internal view returns (
IEscrowFactory.DstEscrowImmutablesCreation memory,
Escrow
) {
(
IEscrowFactory.DstEscrowImmutablesCreation memory escrowImmutables,
bytes memory data
) = _buildDstEscrowImmutables(secret, amount);
address msgSender = bob;
uint256 deployedAt = block.timestamp;
bytes32 salt = keccak256(abi.encodePacked(deployedAt, data, msgSender));
Escrow dstClone = Escrow(escrowFactory.addressOfEscrow(salt));
return (escrowImmutables, dstClone);
}

function _buildDstEscrowImmutables(bytes32 secret, uint256 amount) internal view returns(
IEscrowFactory.DstEscrowImmutablesCreation memory immutables,
bytes memory data
) {
uint256 hashlock = uint256(keccak256(abi.encodePacked(secret)));
uint256 safetyDeposit = amount * 10 / 100;
uint256 srcCancellationTimestamp = block.timestamp + srcTimelocks.finality + srcTimelocks.publicUnlock;
immutables = IEscrowFactory.DstEscrowImmutablesCreation({
hashlock: hashlock,
maker: alice,
taker: bob,
token: address(dai),
amount: amount,
safetyDeposit: safetyDeposit,
timelocks: dstTimelocks,
srcCancellationTimestamp: srcCancellationTimestamp
});
data = abi.encode(
hashlock,
alice,
bob,
block.chainid,
address(dai),
amount,
safetyDeposit,
dstTimelocks.finality,
dstTimelocks.unlock,
dstTimelocks.publicUnlock
);
}
}
Loading

0 comments on commit 067d158

Please sign in to comment.