Skip to content

Commit

Permalink
Extensive documentation + minor refactoring for readability (#20)
Browse files Browse the repository at this point in the history
* doc: comments for `{ERC20,Native}Consideration.sol`

* chore: rename `ERC721SwapperLib` to `ERC721TransferLib`

* chore/doc: move `ERC721TransferLib` structs inside library + document it

* doc: `SWAP2.sol` except for contract-level comments

* doc: improve `SwapperBase` comments

* doc+refactor: document virtual `SwapperDeployerBase_platformFeeConfig()` and move `ISwapperEvents` inheritance to actual deployers

* doc: `Action` type and `ActionMessageLib` library

* doc+refactor: document contract artifacts and change their type to `bytes3`

* doc: structs used for `<T>Swap` fields

* doc: expand on `propose()` security

* doc: post-execution invariant guarantees with native-token consideration

* doc: full documentation of `For{ERC20,Native}Swapper.sol.tmpl`

* doc: complete documentation of `Swapper{Base,Deployer}` templates
  • Loading branch information
ARR4N authored May 21, 2024
1 parent 87ecfb1 commit 7b92450
Show file tree
Hide file tree
Showing 24 changed files with 322 additions and 120 deletions.
13 changes: 13 additions & 0 deletions src/ERC20Consideration.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Consideration, Disbursement, Parties} from "./TypesAndConstants.sol";

/**
* @dev Disburses funds denominated in ERC20.
*/
contract ERC20Consideration {
using SafeERC20 for IERC20;

/**
* @notice Disburses funds denominated in ERC20.
* @param parties Funds are sent from `parties.buyer` to all third-parties, the fee recipient, and `parties.seller`.
* @param c Breakdown of fund disbursement. See `Consideration` documentation for details.
* @param currency ERC20 contract in which `Consideration` is denominated.
* @param feeRecipient Recipient of `fee`.
* @param fee Amount to send to `feeRecipient` from `parties.buyer`, in addition to all `c.thirdParty`
* disbursements.
*/
function _disburseFunds(
Parties memory parties,
Consideration memory c,
Expand Down
4 changes: 2 additions & 2 deletions src/ERC721ForERC20/ERC721ForERC20Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity 0.8.25;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {ERC721Token} from "../ERC721SwapperLib.sol";
import {ERC721TransferLib} from "../ERC721TransferLib.sol";
import {Consideration, Parties} from "../TypesAndConstants.sol";

struct ERC721ForERC20Swap {
Parties parties;
ERC721Token offer;
ERC721TransferLib.ERC721Token offer;
Consideration consideration;
IERC20 currency;
}
4 changes: 2 additions & 2 deletions src/ERC721ForNative/ERC721ForNativeSwap.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ERC721Token} from "../ERC721SwapperLib.sol";
import {ERC721TransferLib} from "../ERC721TransferLib.sol";
import {Consideration, PayableParties} from "../TypesAndConstants.sol";

struct ERC721ForNativeSwap {
PayableParties parties;
ERC721Token offer;
ERC721TransferLib.ERC721Token offer;
Consideration consideration;
}
30 changes: 0 additions & 30 deletions src/ERC721SwapperLib.sol

This file was deleted.

45 changes: 45 additions & 0 deletions src/ERC721TransferLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity ^0.8.24;

import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
import {Parties} from "./TypesAndConstants.sol";

/**
* @dev Transfers one or more ERC721 tokens between parties.
* @dev Note that all `_transfer(<T>, Parties)` functions have effectively the same signature, allowing them to be
* called without explicit knowledge of the <T> type as the constructor will select the respective function. This is
* exploited by the `TMPL/SwapperBase.sol.tmpl` template.
*/
library ERC721TransferLib {
/// @dev Represents a single ERC721 token.
struct ERC721Token {
IERC721 addr;
uint256 id;
}

/// @dev Transfers the token from `parties.seller` to `parties.buyer`.
function _transfer(ERC721Token memory token, Parties memory parties) internal {
token.addr.transferFrom(parties.seller, parties.buyer, token.id);
}

/// @dev Represents multiple ERC721 tokens within the same contract.
struct MultiERC721Token {
IERC721 addr;
uint256[] ids; // MUST be distinct
}

/**
* @dev Transfers all tokens from `parties.seller` to `parties.buyer`.
* @param tokens An _array_ of MultiERC721Token structs, representing any number of tokens. {addr,id} pairs MUST be
* distinct across the entire array.
*/
function _transfer(MultiERC721Token[] memory tokens, Parties memory parties) internal {
for (uint256 i = 0; i < tokens.length; ++i) {
IERC721 t = tokens[i].addr;
for (uint256 j = 0; j < tokens[i].ids.length; ++j) {
t.transferFrom(parties.seller, parties.buyer, tokens[i].ids[j]);
}
}
}
}
4 changes: 2 additions & 2 deletions src/MultiERC721ForERC20/MultiERC721ForERC20Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ pragma solidity 0.8.25;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {Consideration, Parties} from "../TypesAndConstants.sol";
import {MultiERC721Token} from "../ERC721SwapperLib.sol";
import {ERC721TransferLib} from "../ERC721TransferLib.sol";

struct MultiERC721ForERC20Swap {
Parties parties;
MultiERC721Token[] offer;
ERC721TransferLib.MultiERC721Token[] offer;
Consideration consideration;
IERC20 currency;
}
4 changes: 2 additions & 2 deletions src/MultiERC721ForNative/MultiERC721ForNativeSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
pragma solidity 0.8.25;

import {Consideration, PayableParties} from "../TypesAndConstants.sol";
import {MultiERC721Token} from "../ERC721SwapperLib.sol";
import {ERC721TransferLib} from "../ERC721TransferLib.sol";

struct MultiERC721ForNativeSwap {
PayableParties parties;
MultiERC721Token[] offer;
ERC721TransferLib.MultiERC721Token[] offer;
Consideration consideration;
}
24 changes: 24 additions & 0 deletions src/NativeTokenConsideration.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Consideration, Disbursement, PayableParties, InsufficientBalance} from "./TypesAndConstants.sol";

/**
* @dev Disburses funds denominated in the chain's native token.
*/
contract NativeTokenConsideration {
using Address for address payable;

/**
* @notice Disburses funds denominated in the chain's native token.
* @dev If the contract's balance is greater than `c.total`, the excess is sent to `parties.seller`.
* @param parties Funds are sent from `parties.buyer` to all third-parties, the fee recipient, and `parties.seller`.
* @param c Breakdown of fund disbursement. See `Consideration` documentation for details.
* @param feeRecipient Recipient of `fee`.
* @param fee Amount to send to `feeRecipient` from `parties.buyer`, in addition to all `c.thirdParty`
* disbursements.
*/
function _disburseFunds(
PayableParties memory parties,
Consideration memory c,
Expand All @@ -23,17 +36,28 @@ contract NativeTokenConsideration {
for (uint256 i = 0; i < tP.length; ++i) {
payable(tP[i].to).sendValue(tP[i].amount);
}

// MUST remain as the last step to guarantee that _postExecutionInvariantsMet() returns true. This means that
// the only actor capable of griefing the invariants is the seller (by returning some of these funds), which
// would only act to harm themselves.
_sendEntireBalance(parties.seller);
}

/// @notice Sends the contract's entire balance to `parties.buyer`.
function _cancel(PayableParties memory parties) internal virtual {
// MUST remain as the last step for the same reason as _disburseFunds().
_sendEntireBalance(parties.buyer);
}

/**
* @dev Sends the entirety of the contract's balance to the specified address. As this is called as the final step
* in all paths, the post-execution invariant of a zero balance is ensured.
*/
function _sendEntireBalance(address payable to) private {
to.sendValue(address(this).balance);
}

/// @dev Returns whether the contract's remaining balance is zero.
function _postExecutionInvariantsMet() internal view returns (bool) {
return address(this).balance == 0;
}
Expand Down
19 changes: 19 additions & 0 deletions src/SWAP2.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;

import {ERC721ForNativeSwapperDeployer} from "./ERC721ForNative/ERC721ForNativeSwapperDeployer.gen.sol";
Expand All @@ -15,19 +16,37 @@ contract SWAP2 is
MultiERC721ForNativeSwapperDeployer,
MultiERC721ForERC20SwapperDeployer
{
/**
* @param initialOwner Initial owner of the contract. SHOULD be a multisig as this address can modify platform-fee
* configuration.
*/
constructor(address initialOwner) Ownable(initialOwner) {}

/// @dev Packs platform-fee configuration into a single word.
struct PlatformFeeConfig {
address payable recipient;
uint16 basisPoints;
}

/// @notice Platform-fee configuration.
PlatformFeeConfig public feeConfig;

/**
* @notice Sets the platform fee and recipient.
* @dev Evert <T>Swap struct includes a maximum fee, above which the swap will revert, making it impossible for this
* function to front-run a swap unless it's in favour of the parties. In the event of a fee increase, the UI SHOULD
* warn users and begin computing swapper addresses at the higher maximum fee.
* @param recipient Address to which platform fees are sent.
* @param basisPoints One-hundredths of a percentage point of swap consideration to charge as a platform fee.
*/
function setPlatformFee(address payable recipient, uint16 basisPoints) external onlyOwner {
feeConfig = PlatformFeeConfig({recipient: recipient, basisPoints: basisPoints});
}

/**
* @dev Implements virtual function required by all <T>Deployers.
* @return Most recent values passed to `setPlatformFee()`.
*/
function _platformFeeConfig() internal view override returns (address payable, uint16) {
PlatformFeeConfig memory config = feeConfig;
return (config.recipient, config.basisPoints);
Expand Down
20 changes: 14 additions & 6 deletions src/SwapperBase.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;

import {Parties, PayableParties} from "./TypesAndConstants.sol";

/// @dev Base contract for all <T>Swapper implementations.
/**
* @dev Base contract for all <T>Swapper implementations. As implementations are created by simple text substitution
* from templates, they require function overloading based on <T>Swap struct fields; this contract enables such
* behaviour, greatly simplifying code generation.
*/
contract SwapperBase {
/**
* @dev All <T>Swappers call _cancel() with their respective [Payable]Parties, the specific one chosen by the
* compiler.
* @dev Active cancellation isn't needed by all consideration types, hence the empty implementations. If a
* <U>Consideration contract overrides a function then the compiler will guide composition of contracts
* by requiring an explicit override. See `TMPL/ForNativeSwapper.sol.tmpl` as an example.
*/
function _cancel(Parties memory) internal virtual {}
function _cancel(PayableParties memory) internal virtual {}

Expand All @@ -15,11 +27,7 @@ contract SwapperBase {
}
}

/**
* @dev Echoes its argument unchanged. This is a convenience for code that may have either a `Parties` or a
* `PayableParties` but needs the former, greatly simplifying code generation by having the compiler choose the
* correct function based on argument type.
*/
/// @dev Echoes its argument unchanged.
function _asNonPayableParties(Parties memory p) internal pure returns (Parties memory) {
return p;
}
Expand Down
8 changes: 5 additions & 3 deletions src/SwapperDeployerBase.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ISwapperEvents} from "./TypesAndConstants.sol";

/// @dev Abstract base contract for all <T>SwapperDeployer implementations.
abstract contract SwapperDeployerBase is ISwapperEvents {
abstract contract SwapperDeployerBase {
/**
* @return recipient Address to which platform fees MUST be sent by swapper contracts.
* @return basisPoints One-hundredths of a percentage point of swap consideration that MUST be sent to `recipient`.
*/
function _platformFeeConfig() internal view virtual returns (address payable recipient, uint16 basisPoints);
}
16 changes: 11 additions & 5 deletions src/TMPL/ForERC20Swapper.sol.tmpl
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;
/**
* GENERATED CODE - DO NOT EDIT
*/

import {TMPLSwap as Swap} from "./TMPLSwap.sol";
import {TMPLSwap} from "./TMPLSwap.sol";
import {TMPLSwapperBase} from "./TMPLSwapperBase.gen.sol";

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

/// @notice Executes the swap with consideration denominated in the ERC20 denoted by the swap's currency field.
contract TMPLSwapper is TMPLSwapperBase, ERC20Consideration {
constructor(Swap memory swap) TMPLSwapperBase(swap) {}
constructor(TMPLSwap memory swap) TMPLSwapperBase(swap) {}

function _disburseFunds(Swap memory swap, address payable feeRecipient, uint256 fee) internal override {
/**
* @dev Propagates arguments, unchanged, to ERC20Consideration._disburseFunds(), acting only to modify the function
* signature to accept a `TMPLSwap`.
*/
function _disburseFunds(TMPLSwap memory swap, address payable feeRecipient, uint256 fee) internal override {
ERC20Consideration._disburseFunds(swap.parties, swap.consideration, swap.currency, feeRecipient, fee);
}

function _postExecutionInvariantsMet(Swap memory) internal pure override returns (bool) {
// Will be removed by the compiler, but explicitly stating that there are no checks.
/// @dev Always returns true, explicitly stating that there are no checks to be performed.
function _postExecutionInvariantsMet(TMPLSwap memory) internal pure override returns (bool) {
return true;
}
}
19 changes: 15 additions & 4 deletions src/TMPL/ForNativeSwapper.sol.tmpl
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright 2024 Divergence Tech Ltd.
pragma solidity 0.8.25;
/**
* GENERATED CODE - DO NOT EDIT
*/

import {TMPLSwap as Swap} from "./TMPLSwap.sol";
import {TMPLSwap} from "./TMPLSwap.sol";
import {TMPLSwapperBase} from "./TMPLSwapperBase.gen.sol";

import {NativeTokenConsideration} from "../NativeTokenConsideration.sol";
import {SwapperBase} from "../SwapperBase.sol";
import {PayableParties} from "../TypesAndConstants.sol";

/// @notice Executes the swap with consideration denominated in the chain's native toiken.
contract TMPLSwapper is TMPLSwapperBase, NativeTokenConsideration {
constructor(Swap memory swap) payable TMPLSwapperBase(swap) {}
constructor(TMPLSwap memory swap) payable TMPLSwapperBase(swap) {}

function _disburseFunds(Swap memory swap, address payable feeRecipient, uint256 fee) internal override {
/**
* @dev Propagates arguments, unchanged, to NativeTokenConsideration._disburseFunds(), acting only to modify the
* function signature to accept a `TMPLSwap`.
*/
function _disburseFunds(TMPLSwap memory swap, address payable feeRecipient, uint256 fee) internal override {
NativeTokenConsideration._disburseFunds(swap.parties, swap.consideration, feeRecipient, fee);
}

/// @dev Required override; propagates `p`, unchanged, to NativeTokenConsideration._cancel().
function _cancel(PayableParties memory p) internal override(SwapperBase, NativeTokenConsideration) {
NativeTokenConsideration._cancel(p);
}

function _postExecutionInvariantsMet(Swap memory) internal view override returns (bool) {
/**
* @dev Returns NativeTokenConsideration._postExecutionInvariantsMet(), acting only to modify the function signature
* to accept a `TMPLSwap`.
*/
function _postExecutionInvariantsMet(TMPLSwap memory) internal view override returns (bool) {
return NativeTokenConsideration._postExecutionInvariantsMet();
}
}
Loading

0 comments on commit 7b92450

Please sign in to comment.