Skip to content

Commit

Permalink
Merge pull request #9 from 1inch/SC-1043-Add-NatSpec-docs
Browse files Browse the repository at this point in the history
[SC-1043] Add NatSpec docs
  • Loading branch information
byshape authored Jan 18, 2024
2 parents 55e9488 + 1a5b8de commit 21d28c1
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 47 deletions.
22 changes: 11 additions & 11 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
EscrowFactoryTest:testFuzz_DeployCloneForMaker(bytes32,uint56,uint56) (runs: 256, μ: 318009, ~: 321353)
EscrowFactoryTest:testFuzz_DeployCloneForTaker(bytes32,uint56) (runs: 256, μ: 244285, ~: 247826)
EscrowFactoryTest:testFuzz_DeployCloneForMaker(bytes32,uint56,uint56) (runs: 256, μ: 317514, ~: 321353)
EscrowFactoryTest:testFuzz_DeployCloneForTaker(bytes32,uint56) (runs: 256, μ: 243672, ~: 247826)
EscrowFactoryTest:test_NoDeploymentForNotResolver() (gas: 112084)
EscrowFactoryTest:test_NoInsufficientBalanceDeploymentForMaker() (gas: 281570)
EscrowFactoryTest:test_NoInsufficientBalanceDeploymentForTaker() (gas: 30637)
Expand All @@ -14,16 +14,16 @@ EscrowTest:test_NoCancelDuringResolverUnlockDst() (gas: 237840)
EscrowTest:test_NoCancelDuringUnlockSrc() (gas: 319525)
EscrowTest:test_NoFailedNativeTokenTransferCancelDst() (gas: 249083)
EscrowTest:test_NoFailedNativeTokenTransferCancelSrc() (gas: 335638)
EscrowTest:test_NoFailedNativeTokenTransferWithdrawalDst() (gas: 271091)
EscrowTest:test_NoFailedNativeTokenTransferWithdrawalSrc() (gas: 352888)
EscrowTest:test_NoWithdrawalByNonResolverDst() (gas: 238107)
EscrowTest:test_NoFailedNativeTokenTransferWithdrawalDst() (gas: 271094)
EscrowTest:test_NoFailedNativeTokenTransferWithdrawalSrc() (gas: 352891)
EscrowTest:test_NoWithdrawalByNonResolverDst() (gas: 238110)
EscrowTest:test_NoWithdrawalDuringFinalityLockDst() (gas: 237218)
EscrowTest:test_NoWithdrawalDuringFinalityLockSrc() (gas: 319286)
EscrowTest:test_NoWithdrawalWithWrongSecretDst() (gas: 238982)
EscrowTest:test_NoWithdrawalWithWrongSecretSrc() (gas: 319933)
EscrowTest:test_WithdrawByAnyoneDst() (gas: 255281)
EscrowTest:test_WithdrawByResolverDst() (gas: 255927)
EscrowTest:test_WithdrawByResolverPublicDst() (gas: 255644)
EscrowTest:test_WithdrawSrc() (gas: 336713)
EscrowTest:test_NoWithdrawalWithWrongSecretDst() (gas: 238985)
EscrowTest:test_NoWithdrawalWithWrongSecretSrc() (gas: 319936)
EscrowTest:test_WithdrawByAnyoneDst() (gas: 255284)
EscrowTest:test_WithdrawByResolverDst() (gas: 255930)
EscrowTest:test_WithdrawByResolverPublicDst() (gas: 255647)
EscrowTest:test_WithdrawSrc() (gas: 336716)
IntegrationEscrowFactoryTest:testFuzz_DeployCloneForMakerInt(bytes32,uint56,uint56) (runs: 256, μ: 397861, ~: 401576)
IntegrationEscrowFactoryTest:test_NoInsufficientBalanceDeploymentForMakerInt() (gas: 382262)
92 changes: 79 additions & 13 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ import { SafeERC20 } from "solidity-utils/libraries/SafeERC20.sol";

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

/**
* @title Escrow contract for cross-chain atomic swap.
* @notice Contract to initially lock funds on both chains and then unlock with verification of the secret presented.
* @dev Funds are locked in at the time of contract deployment. On both chains this is done by calling `EscrowFactory`
* functions. On the source chain Limit Order Protocol calls the `postInteraction` function and on the destination
* chain taker calls the `createEscrow` function.
* Withdrawal and cancellation functions for the source and destination chains are implemented separately.
*/
contract Escrow is Clone, IEscrow {
using SafeERC20 for IERC20;

/**
* @notice See {IEscrow-withdrawSrc}.
*/
function withdrawSrc(bytes32 secret) external {
SrcEscrowImmutables calldata escrowImmutables = srcEscrowImmutables();
uint256 finalityTimestamp = escrowImmutables.deployedAt + escrowImmutables.extraDataParams.srcTimelocks.finality;
uint256 finalisedTimestamp = escrowImmutables.deployedAt + escrowImmutables.extraDataParams.srcTimelocks.finality;
// Check that it's public unlock period.
if (
block.timestamp < finalityTimestamp ||
block.timestamp > finalityTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock
block.timestamp < finalisedTimestamp ||
block.timestamp >= finalisedTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock
) revert InvalidWithdrawalTime();

_checkSecretAndTransfer(
Expand All @@ -27,18 +39,25 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.interactionParams.srcToken,
escrowImmutables.interactionParams.srcAmount
);

// Send the safety deposit to the caller.
(bool success, ) = msg.sender.call{value: escrowImmutables.extraDataParams.srcSafetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

/**
* @notice See {IEscrow-cancelSrc}.
*/
function cancelSrc() external {
SrcEscrowImmutables calldata escrowImmutables = srcEscrowImmutables();
uint256 finalityTimestamp = escrowImmutables.deployedAt + escrowImmutables.extraDataParams.srcTimelocks.finality;
uint256 cancellationTimestamp = finalityTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock;
uint256 finalisedTimestamp = escrowImmutables.deployedAt + escrowImmutables.extraDataParams.srcTimelocks.finality;
uint256 cancellationTimestamp = finalisedTimestamp + escrowImmutables.extraDataParams.srcTimelocks.publicUnlock;
// Check that it's cancellation period.
if (block.timestamp < cancellationTimestamp) {
revert InvalidCancellationTime();
}

// Check that the caller is a taker if it's the private cancellation period.
if (
block.timestamp < cancellationTimestamp + escrowImmutables.extraDataParams.srcTimelocks.cancel &&
msg.sender != escrowImmutables.interactionParams.taker
Expand All @@ -50,20 +69,27 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.interactionParams.maker,
escrowImmutables.interactionParams.srcAmount
);

// Send the safety deposit to the caller.
(bool success, ) = msg.sender.call{value: escrowImmutables.extraDataParams.srcSafetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

/**
* @notice See {IEscrow-withdrawDst}.
*/
function withdrawDst(bytes32 secret) external {
DstEscrowImmutables calldata escrowImmutables = dstEscrowImmutables();
uint256 finalityTimestamp = escrowImmutables.deployedAt + escrowImmutables.timelocks.finality;
uint256 unlockTimestamp = finalityTimestamp + escrowImmutables.timelocks.unlock;
uint256 finalisedTimestamp = escrowImmutables.deployedAt + escrowImmutables.timelocks.finality;
uint256 publicUnlockTimestamp = finalisedTimestamp + escrowImmutables.timelocks.unlock;
// Check that it's an unlock period.
if (
block.timestamp < finalityTimestamp ||
block.timestamp > unlockTimestamp + escrowImmutables.timelocks.publicUnlock
block.timestamp < finalisedTimestamp ||
block.timestamp >= publicUnlockTimestamp + escrowImmutables.timelocks.publicUnlock
) revert InvalidWithdrawalTime();

if (block.timestamp < unlockTimestamp && msg.sender != escrowImmutables.taker) revert InvalidCaller();
// Check that the caller is a taker if it's the private unlock period.
if (block.timestamp < publicUnlockTimestamp && msg.sender != escrowImmutables.taker) revert InvalidCaller();

_checkSecretAndTransfer(
secret,
Expand All @@ -72,14 +98,23 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.token,
escrowImmutables.amount
);

// Send the safety deposit to the caller.
(bool success, ) = msg.sender.call{value: escrowImmutables.safetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

/**
* @notice See {IEscrow-cancelDst}.
*/
function cancelDst() external {
DstEscrowImmutables calldata escrowImmutables = dstEscrowImmutables();
uint256 finalityTimestamp = escrowImmutables.deployedAt + escrowImmutables.timelocks.finality;
if (block.timestamp < finalityTimestamp + escrowImmutables.timelocks.unlock + escrowImmutables.timelocks.publicUnlock) {
uint256 finalisedTimestamp = escrowImmutables.deployedAt + escrowImmutables.timelocks.finality;
// Check that it's a cancellation period.
if (
block.timestamp <
finalisedTimestamp + escrowImmutables.timelocks.unlock + escrowImmutables.timelocks.publicUnlock
) {
revert InvalidCancellationTime();
}

Expand All @@ -88,27 +123,58 @@ contract Escrow is Clone, IEscrow {
escrowImmutables.amount
);

// Send the safety deposit to the caller.
(bool success, ) = msg.sender.call{value: escrowImmutables.safetyDeposit}("");
if (!success) revert NativeTokenSendingFailure();
}

/**
* @notice See {IEscrow-srcEscrowImmutables}.
*/
function srcEscrowImmutables() public pure returns (SrcEscrowImmutables calldata data) {
// Get the offset of the immutable args in calldata.
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") { data := offset }
}

/**
* @notice See {IEscrow-dstEscrowImmutables}.
*/
function dstEscrowImmutables() public pure returns (DstEscrowImmutables calldata data) {
// Get the offset of the immutable args in calldata.
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") { data := offset }
}

/**
* @notice Verifies the provided secret.
* @dev The secret is valid if its hash matches the hashlock.
* @param secret Provided secret to verify.
* @param hashlock Hashlock to compare with.
* @return True if the secret is valid, false otherwise.
*/
function _isValidSecret(bytes32 secret, bytes32 hashlock) internal pure returns (bool) {
return keccak256(abi.encode(secret)) == hashlock;
}

function _checkSecretAndTransfer(bytes32 secret, bytes32 hashlock, address recipient, address token, uint256 amount) internal {
/**
* @notice Checks the secret and transfers tokens to the recipient.
* @dev The secret is valid if its hash matches the hashlock.
* @param secret Provided secret to verify.
* @param hashlock Hashlock to compare with.
* @param recipient Address to transfer tokens to.
* @param token Address of the token to transfer.
* @param amount Amount of tokens to transfer.
*/
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
28 changes: 25 additions & 3 deletions contracts/EscrowFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ import { ClonesWithImmutableArgs } from "clones-with-immutable-args/ClonesWithIm
import { IEscrow } from "./interfaces/IEscrow.sol";
import { IEscrowFactory } from "./interfaces/IEscrowFactory.sol";


/**
* @title Escrow Factory contract
* @notice Contract to create escrow contracts for cross-chain atomic swap.
*/
contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {
using AddressLib for Address;
using ClonesWithImmutableArgs for address;
using SafeERC20 for IERC20;

// Address of the escrow contract implementation to clone.
address public immutable IMPLEMENTATION;

constructor(address implementation, address limitOrderProtocol, IERC20 token)
Expand All @@ -26,7 +32,11 @@ contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {
}

/**
* @dev Creates a new escrow contract for maker.
* @notice Creates a new escrow contract for maker on the source chain.
* @dev The caller must be whitelisted and pre-send the safety deposit in a native token
* to a pre-computed deterministic address of the created escrow.
* The external postInteraction function call will be made from the Limit Order Protocol
* after all funds have been transferred. See {IPostInteraction-postInteraction}.
*/
function _postInteraction(
IOrderMixin.Order calldata order,
Expand All @@ -43,9 +53,10 @@ contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {

bytes calldata extraDataParams = extraData[:352];
bytes calldata whitelist = extraData[352:];

if (!_isWhitelisted(whitelist, taker)) revert ResolverIsNotWhitelisted();

// Prepare immutables for the escrow contract.
bytes memory interactionParams = abi.encode(
order.maker,
taker,
Expand All @@ -59,6 +70,7 @@ contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {
interactionParams,
extraDataParams
);

// Salt is orderHash
address escrow = _createEscrow(data, orderHash, 0);
uint256 safetyDeposit = abi.decode(extraDataParams, (IEscrow.ExtraDataParams)).srcSafetyDeposit;
Expand All @@ -71,7 +83,7 @@ contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {
}

/**
* @dev Creates a new escrow contract for taker.
* @notice See {IEscrowFactory-createEscrow}.
*/
function createEscrow(DstEscrowImmutablesCreation calldata dstEscrowImmutables) external payable {
if (msg.value < dstEscrowImmutables.safetyDeposit) revert InsufficientEscrowBalance();
Expand Down Expand Up @@ -104,10 +116,20 @@ contract EscrowFactory is IEscrowFactory, SimpleSettlementExtension {
);
}

/**
* @notice See {IEscrowFactory-addressOfEscrow}.
*/
function addressOfEscrow(bytes32 salt) public view returns (address) {
return address(uint160(ClonesWithImmutableArgs.addressOfClone3(salt)));
}

/**
* @notice Creates a new escrow contract with immutable arguments.
* @dev The escrow contract is a proxy clone created using the create3 pattern.
* @param data Encoded immutable args.
* @param salt The salt that influences the contract address in deterministic deployment.
* @return clone The address of the created escrow contract.
*/
function _createEscrow(
bytes memory data,
bytes32 salt,
Expand Down
59 changes: 59 additions & 0 deletions contracts/interfaces/IEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface IEscrow {
uint256 unlock;
uint256 publicUnlock;
}

// Data for the immutables from the order post interacton.
struct InteractionParams {
address maker;
address taker;
Expand All @@ -36,7 +38,9 @@ interface IEscrow {
uint256 dstAmount;
}

// Data for the immutables from the order extension.
struct ExtraDataParams {
// Hash of the secret.
bytes32 hashlock;
uint256 dstChainId;
address dstToken;
Expand All @@ -46,14 +50,20 @@ interface IEscrow {
DstTimelocks dstTimelocks;
}

// Data for the source chain order immutables.
struct SrcEscrowImmutables {
uint256 deployedAt;
InteractionParams interactionParams;
ExtraDataParams extraDataParams;
}

/**
* Data for the destination chain order immutables.
* chainId, token, amount and safetyDeposit relate to the destination chain.
*/
struct DstEscrowImmutables {
uint256 deployedAt;
// Hash of the secret.
bytes32 hashlock;
address maker;
address taker;
Expand All @@ -69,4 +79,53 @@ interface IEscrow {
error InvalidSecret();
error InvalidWithdrawalTime();
error NativeTokenSendingFailure();

/**
* @notice Withdraws funds to the taker on the source chain.
* @dev Withdrawal can only be made during the public unlock period and with secret
* with hash matches the hashlock.
* The safety deposit is sent to the caller.
* @param secret The secret that unlocks the escrow.
*/
function withdrawSrc(bytes32 secret) external;

/**
* @notice Cancels the escrow on the source chain and returns tokens to the maker.
* @dev The escrow can only be cancelled by taker during the private cancel period or
* by anyone during the public cancel period.
* The safety deposit is sent to the caller.
*/
function cancelSrc() external;

/**
* @notice Withdraws funds to the maker on the destination chain.
* @dev Withdrawal can only be made by taker during the private unlock period or by anyone
* during the public unlock period. In both cases, a secret with hash matching the hashlock must be provided.
* The safety deposit is sent to the caller.
* @param secret The secret that unlocks the escrow.
*/
function withdrawDst(bytes32 secret) external;

/**
* @notice Cancels the escrow on the destination chain and returns tokens to the taker.
* @dev The escrow can only be cancelled during the cancel period.
* The safety deposit is sent to the caller.
*/
function cancelDst() external;

/**
* @notice Returns the immutable parameters of the escrow contract on the source chain.
* @dev The immutables are stored at the end of the proxy clone contract bytecode and
* are added to the calldata each time the proxy clone function is called.
* @return The immutables of the escrow contract.
*/
function srcEscrowImmutables() external pure returns (SrcEscrowImmutables calldata);

/**
* @notice Returns the immutable parameters of the escrow contract on the destination chain.
* @dev The immutables are stored at the end of the proxy clone contract bytecode and
* are added to the calldata each time the proxy clone function is called.
* @return The immutables of the escrow contract.
*/
function dstEscrowImmutables() external pure returns (DstEscrowImmutables calldata);
}
Loading

0 comments on commit 21d28c1

Please sign in to comment.