Skip to content

Commit

Permalink
Added safety deposit for the source chain
Browse files Browse the repository at this point in the history
  • Loading branch information
byshape committed Jan 11, 2024
1 parent 729b74a commit de6e161
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 67 deletions.
37 changes: 22 additions & 15 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,31 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.interactionParams.srcToken,
escrowImmutables.interactionParams.srcAmount
);
(bool success, ) = msg.sender.call{value: escrowImmutables.extraDataParams.srcSafetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

function cancelSrc() external {
SrcEscrowImmutables calldata escrowImmutables = srcEscrowImmutables();
uint256 finalityTimestamp = escrowImmutables.deployedAt + escrowImmutables.extraDataParams.srcTimelocks.finality;
if (block.timestamp < finalityTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock) {
uint256 cancellationTimestamp = finalityTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock;
if (block.timestamp < cancellationTimestamp) {
revert InvalidCancellationTime();
}

if (
block.timestamp < cancellationTimestamp + escrowImmutables.extraDataParams.srcTimelocks.cancel &&
msg.sender != escrowImmutables.interactionParams.taker
) {
revert InvalidCaller();
}

IERC20(escrowImmutables.interactionParams.srcToken).safeTransfer(
escrowImmutables.interactionParams.maker,
escrowImmutables.interactionParams.srcAmount
);
(bool success, ) = msg.sender.call{value: escrowImmutables.extraDataParams.srcSafetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

function withdrawDst(bytes32 secret) external {
Expand All @@ -51,20 +63,17 @@ contract Escrow is Clone, IEscrow {
block.timestamp > unlockTimestamp + escrowImmutables.timelocks.publicUnlock
) revert InvalidWithdrawalTime();

if (block.timestamp < unlockTimestamp) {
if (msg.sender != escrowImmutables.taker) revert InvalidCaller();
}
if (block.timestamp < unlockTimestamp && msg.sender != escrowImmutables.taker) revert InvalidCaller();

_checkSecretAndTransfer(
secret,
escrowImmutables.hashlock,
escrowImmutables.maker,
escrowImmutables.token,
escrowImmutables.amount
);
IERC20(escrowImmutables.token).safeTransfer(
msg.sender,
escrowImmutables.safetyDeposit
);
(bool success, ) = msg.sender.call{value: escrowImmutables.safetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

function cancelDst() external {
Expand All @@ -79,10 +88,8 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.amount
);

IERC20(escrowImmutables.token).safeTransfer(
msg.sender,
escrowImmutables.safetyDeposit
);
(bool success, ) = msg.sender.call{value: escrowImmutables.safetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

function srcEscrowImmutables() public pure returns (SrcEscrowImmutables calldata) {
Expand All @@ -101,11 +108,11 @@ contract Escrow is Clone, IEscrow {
return data;
}

function _isValidSecret(bytes32 secret, uint256 hashlock) internal pure returns (bool) {
return uint256(keccak256(abi.encode(secret))) == hashlock;
function _isValidSecret(bytes32 secret, bytes32 hashlock) internal pure returns (bool) {
return keccak256(abi.encode(secret)) == hashlock;
}

function _checkSecretAndTransfer(bytes32 secret, uint256 hashlock, address recipient, address token, uint256 amount) internal {
function _checkSecretAndTransfer(bytes32 secret, bytes32 hashlock, address recipient, address token, uint256 amount) internal {
if (!_isValidSecret(secret, hashlock)) revert InvalidSecret();
IERC20(token).safeTransfer(recipient, amount);
}
Expand Down
23 changes: 17 additions & 6 deletions contracts/EscrowFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Address, AddressLib } from "solidity-utils/libraries/AddressLib.sol";
import { SafeERC20 } from "solidity-utils/libraries/SafeERC20.sol";
import { ClonesWithImmutableArgs } from "clones-with-immutable-args/ClonesWithImmutableArgs.sol";

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

contract EscrowFactory is IEscrowFactory, ExtensionBase {
Expand Down Expand Up @@ -51,14 +52,19 @@ contract EscrowFactory is IEscrowFactory, ExtensionBase {
);
// Salt is orderHash
address escrow = ClonesWithImmutableArgs.addressOfClone3(orderHash);
if (IERC20(order.makerAsset.get()).balanceOf(escrow) < makingAmount) revert InsufficientEscrowBalance();
uint256 safetyDeposit = abi.decode(extraData, (IEscrow.ExtraDataParams)).srcSafetyDeposit;
if (
escrow.balance < safetyDeposit ||
IERC20(order.makerAsset.get()).balanceOf(escrow) < makingAmount
) revert InsufficientEscrowBalance();
_createEscrow(data, orderHash);
}

/**
* @dev Creates a new escrow contract for taker.
*/
function createEscrow(DstEscrowImmutablesCreation calldata dstEscrowImmutables) external {
function createEscrow(DstEscrowImmutablesCreation calldata dstEscrowImmutables) external payable {
if (msg.value < dstEscrowImmutables.safetyDeposit) revert InsufficientEscrowBalance();
// Check that the escrow cancellation will start not later than the cancellation time on the source chain.
if (
block.timestamp +
Expand All @@ -81,14 +87,19 @@ contract EscrowFactory is IEscrowFactory, ExtensionBase {
dstEscrowImmutables.timelocks.publicUnlock
);
bytes32 salt = keccak256(abi.encodePacked(data, msg.sender));
address escrow = _createEscrow(data, salt);

address escrow = addressOfEscrow(salt);
(bool success, ) = escrow.call{value: dstEscrowImmutables.safetyDeposit}("");
if (!success) revert IEscrow.NativeTokenSendingFailure();

_createEscrow(data, salt);
IERC20(dstEscrowImmutables.token).safeTransferFrom(
msg.sender, escrow, dstEscrowImmutables.amount + dstEscrowImmutables.safetyDeposit
msg.sender, escrow, dstEscrowImmutables.amount
);
}

function addressOfEscrow(bytes32 salt) external view returns (address) {
return ClonesWithImmutableArgs.addressOfClone3(salt);
function addressOfEscrow(bytes32 salt) public view returns (address) {
return address(uint160(ClonesWithImmutableArgs.addressOfClone3(salt)));
}

function _createEscrow(
Expand Down
22 changes: 18 additions & 4 deletions contracts/interfaces/IEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ pragma solidity ^0.8.0;

interface IEscrow {
// TODO: is it possible to optimise this?
// Timelocks represent the duration of each period, in seconds
/**
* Timelocks for the source chain.
* finality: The duration of the chain finality period.
* publicUnlock: The duration of the period when anyone with a secret can withdraw tokens for the taker.
* cancel: The duration of the period when escrow can only be cancelled by the taker.
*/
struct SrcTimelocks {
uint256 finality;
uint256 publicUnlock;
uint256 cancel;
}

/**
* Timelocks for the destination chain.
* finality: The duration of the chain finality period.
* unlock: The duration of the period when only the taker with a secret can withdraw tokens for the maker.
* publicUnlock publicUnlock: The duration of the period when anyone with a secret can withdraw tokens for the maker.
*/
struct DstTimelocks {
uint256 finality;
uint256 unlock;
Expand All @@ -25,10 +37,11 @@ interface IEscrow {
}

struct ExtraDataParams {
uint256 hashlock;
bytes32 hashlock;
uint256 dstChainId;
address dstToken;
uint256 safetyDeposit;
uint256 srcSafetyDeposit;
uint256 dstSafetyDeposit;
SrcTimelocks srcTimelocks;
DstTimelocks dstTimelocks;
}
Expand All @@ -41,7 +54,7 @@ interface IEscrow {

struct DstEscrowImmutables {
uint256 deployedAt;
uint256 hashlock;
bytes32 hashlock;
address maker;
address taker;
uint256 chainId;
Expand All @@ -55,4 +68,5 @@ interface IEscrow {
error InvalidCancellationTime();
error InvalidSecret();
error InvalidWithdrawalTime();
error NativeTokenSendingFailure();
}
2 changes: 1 addition & 1 deletion contracts/interfaces/IEscrowFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IEscrow } from "./IEscrow.sol";

interface IEscrowFactory {
struct DstEscrowImmutablesCreation {
uint256 hashlock;
bytes32 hashlock;
address maker;
address taker;
address token;
Expand Down
10 changes: 8 additions & 2 deletions test/integration/EscrowFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ contract IntegrationEscrowFactoryTest is BaseSetup {
0 // threshold
);

(bool success,) = address(srcClone).call{value: uint64(srcAmount) * 10 / 100}("");
assertEq(success, true);

vm.prank(bob.addr);
limitOrderProtocol.fillOrderArgs(
order,
Expand All @@ -48,7 +51,7 @@ contract IntegrationEscrowFactoryTest is BaseSetup {
);

IEscrow.SrcEscrowImmutables memory returnedImmutables = srcClone.srcEscrowImmutables();
assertEq(returnedImmutables.extraDataParams.hashlock, uint256(keccak256(abi.encodePacked(secret))));
assertEq(returnedImmutables.extraDataParams.hashlock, keccak256(abi.encodePacked(secret)));
assertEq(returnedImmutables.interactionParams.srcAmount, srcAmount);
assertEq(returnedImmutables.extraDataParams.dstToken, address(dai));
}
Expand All @@ -59,7 +62,7 @@ contract IntegrationEscrowFactoryTest is BaseSetup {
bytes32 orderHash,
/* bytes memory extraData */,
bytes memory extension,
/* Escrow srcClone */
Escrow srcClone
) = _prepareDataSrc(SECRET, MAKING_AMOUNT, TAKING_AMOUNT, false);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(alice, orderHash);
Expand All @@ -76,6 +79,9 @@ contract IntegrationEscrowFactoryTest is BaseSetup {
0 // threshold
);

(bool success,) = address(srcClone).call{value: SRC_SAFETY_DEPOSIT}("");
assertEq(success, true);

vm.prank(bob.addr);
vm.expectRevert(IEscrowFactory.InsufficientEscrowBalance.selector);
limitOrderProtocol.fillOrderArgs(
Expand Down
Loading

0 comments on commit de6e161

Please sign in to comment.