Skip to content

Commit

Permalink
Version 2.3.0 (#10)
Browse files Browse the repository at this point in the history
Added payment helpers.
`TransferHelpers`
- an abstract contract that safely transfers ETH and ERC-20 tokens

`RoyaltyPayoutHelper`
- an abstract contract that uses the royalty registry to get royalty
recipients for a token sale and payout the royalties safely
  • Loading branch information
mpeyfuss authored Aug 21, 2023
1 parent f3bb929 commit 2476afa
Show file tree
Hide file tree
Showing 29 changed files with 1,099 additions and 46 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/royalty-registry-solidity"]
path = lib/royalty-registry-solidity
url = https://github.com/manifoldxyz/royalty-registry-solidity
22 changes: 16 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,30 @@ remove:

# Install the modules
install:
forge install foundry-rs/forge-std
forge install OpenZeppelin/openzeppelin-contracts@v4.8.3
forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v4.8.3
forge install foundry-rs/forge-std --no-commit
forge install OpenZeppelin/openzeppelin-contracts@v4.8.3 --no-commit
forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v4.8.3 --no-commit
forge install manifoldxyz/royalty-registry-solidity --no-commit

# Updatee the modules
update: remove install

# Builds
build:
forge fmt && forge clean && forge build --optimize --optimizer-runs 2000
forge fmt && forge clean && forge build

# Tests
test_suite:
compiler_test:
forge test --use 0.8.17
forge test --use 0.8.18
forge test --use 0.8.19
forge test --use 0.8.20
forge test --use 0.8.20

quick_test:
forge test --fuzz-runs 512

gas_test:
forge test --gas-report

fuzz_test:
forge test --fuzz-runs 10000
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ This codebase is provided on an "as is" and "as available" basis.
We do not give any warranties and will not be liable for any loss incurred through any use of this codebase.

## License
This code is copyright Transient Labs, Inc 2022 and is licensed under the MIT license.
This code is copyright Transient Labs, Inc 2023 and is licensed under the MIT license.
31 changes: 11 additions & 20 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
[profile.default]
src = 'src' # the source directory
test = 'test' # the test directory
out = 'out' # the output directory (for artifacts)
libs = ['lib'] # a list of library directories
libraries = [] # a list of deployed libraries to link against
cache = true # whether to cache builds or not
force = false # whether to ignore the cache (clean build)
evm_version = 'london' # the evm version (by hardfork name)
#solc_version = '0.8.17' # override for the solc version (setting this ignores `auto_detect_solc`)
auto_detect_solc = true # enable auto-detection of the appropriate solc version to use
offline = false # disable downloading of missing solc version(s)
optimizer = true # enable or disable the solc optimizer
optimizer_runs = 2000 # the number of optimizer runs
verbosity = 3 # the verbosity of tests
ffi = false # whether to enable ffi or not
gas_reports = ["*"]
fs_permissions = [{ access = 'read', path = './test/file_utils'}]

[fuzz]
runs = 10000
src = 'src'
test = 'test'
out = 'out'
libs = ['lib']
auto_detect_solc = true
optimizer = true
optimizer_runs = 20000
verbosity = 3
wrap_comments = true
gas_reports = ["OwnableAccessControl", "EIP2981TL", "OwnableAccessControlUpgradeable", "EIP2981TLUpgradeable", "TransferHelper", "RoyaltyPayoutHelper", "RoyaltyPayoutHelperUpgradeable"]
fs_permissions = [{ access = "read", path = "./"}]
1 change: 1 addition & 0 deletions lib/royalty-registry-solidity
15 changes: 13 additions & 2 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
forge-std/=lib/forge-std/src
@manifoldxyz/libraries-solidity/=lib/royalty-registry-solidity/lib/libraries-solidity/
@openzeppelin/contracts-upgradeable/=lib/royalty-registry-solidity/lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts/=lib/royalty-registry-solidity/lib/openzeppelin-contracts/contracts/
create2-helpers/=lib/royalty-registry-solidity/lib/create2-helpers/
create2-scripts/=lib/royalty-registry-solidity/lib/create2-helpers/script/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
libraries-solidity/=lib/royalty-registry-solidity/lib/libraries-solidity/contracts/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
openzeppelin/=lib/openzeppelin-contracts/contracts/
openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
royalty-registry-solidity/=lib/royalty-registry-solidity/contracts
tl-sol-tools/=src/
2 changes: 1 addition & 1 deletion src/access/OwnableAccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ error NotRoleOrOwner(bytes32 role);
/// @dev by default, only the owner can grant roles but by inheriting, but you
/// may allow other roles to grant roles by using the internal helper.
/// @author transientlabs.xyz
/// @custom:version 2.2.2
/// @custom:last-updated 2.2.2
abstract contract OwnableAccessControl is Ownable {
/*//////////////////////////////////////////////////////////////////////////
State Variables
Expand Down
8 changes: 8 additions & 0 deletions src/payments/IWETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
function deposit() external payable;
}
91 changes: 91 additions & 0 deletions src/payments/RoyaltyPayoutHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {TransferHelper} from "./TransferHelper.sol";
import {IRoyaltyEngineV1} from "royalty-registry-solidity/IRoyaltyEngineV1.sol";

/*//////////////////////////////////////////////////////////////////////////
Royalty Payout Helper
//////////////////////////////////////////////////////////////////////////*/

/// @title Royalty Payout Helper
/// @notice Abstract contract to help payout royalties using the Royalty Registry
/// @author transientlabs.xyz
/// @custom:last-updated 2.3.0
abstract contract RoyaltyPayoutHelper is TransferHelper {
/*//////////////////////////////////////////////////////////////////////////
State Variables
//////////////////////////////////////////////////////////////////////////*/

address public weth;
IRoyaltyEngineV1 public royaltyEngine;

/*//////////////////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////////////////*/

/// @param wethAddress - the init weth address
/// @param royaltyEngineAddress - the init royalty engine address
constructor(address wethAddress, address royaltyEngineAddress) {
weth = wethAddress;
royaltyEngine = IRoyaltyEngineV1(royaltyEngineAddress);
}

/*//////////////////////////////////////////////////////////////////////////
Internal State Functions
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to update the WETH address
/// @dev Care should be taken to ensure proper access control for this function
/// @param wethAddress The new WETH token address
function _setWethAddress(address wethAddress) internal {
weth = wethAddress;
}

/// @notice Function to update the royalty engine address
/// @dev Care should be taken to ensure proper access control for this function
/// @param royaltyEngineAddress The new royalty engine address
function _setRoyaltyEngineAddress(address royaltyEngineAddress) internal {
royaltyEngine = IRoyaltyEngineV1(royaltyEngineAddress);
}

/*//////////////////////////////////////////////////////////////////////////
Royalty Payout Function
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to payout royalties from the contract balance based on sale price
/// @dev if the call to the royalty engine reverts or if the return values are invalid, no payments are made
/// @dev if the sum of the royalty payouts is greater than the salePrice, the loop exits early for gas savings (this shouldn't happen in reality)
/// @dev if this is used in a call where tokens should be transferred from a sender, it is advisable to
/// first transfer the required amount to the contract and then call this function, as it will save on gas
/// @param token The contract address for the token
/// @param tokenId The token id
/// @param currency The address of the currency to send to recipients (null address == ETH)
/// @param salePrice The sale price for the token
/// @return remainingSale The amount left over in the sale after paying out royalties
function _payoutRoyalties(address token, uint256 tokenId, address currency, uint256 salePrice)
internal
returns (uint256 remainingSale)
{
remainingSale = salePrice;
try royaltyEngine.getRoyalty(token, tokenId, salePrice) returns (
address payable[] memory recipients, uint256[] memory amounts
) {
if (recipients.length != amounts.length) return remainingSale;

for (uint256 i = 0; i < recipients.length; i++) {
if (amounts[i] > remainingSale) break;
remainingSale -= amounts[i];
if (currency == address(0)) {
_safeTransferETH(recipients[i], amounts[i], weth);
} else {
_safeTransferERC20(recipients[i], currency, amounts[i]);
}
}

return remainingSale;
} catch {
return remainingSale;
}
}
}
82 changes: 82 additions & 0 deletions src/payments/TransferHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {IWETH, IERC20} from "./IWETH.sol";

/*//////////////////////////////////////////////////////////////////////////
Custom Errors
//////////////////////////////////////////////////////////////////////////*/

/// @dev ETH transfer failed
error ETHTransferFailed();

/// @dev Transferred too few ERC-20 tokens
error InsufficentERC20Transfer();

/*//////////////////////////////////////////////////////////////////////////
Transfer Helper
//////////////////////////////////////////////////////////////////////////*/

/// @title Transfer Helper
/// @notice Abstract contract that has helper function for sending ETH and ERC20's safely
/// @author transientlabs.xyz
/// @custom:last-updated 2.3.0
abstract contract TransferHelper {
/*//////////////////////////////////////////////////////////////////////////
State Variables
//////////////////////////////////////////////////////////////////////////*/

using SafeERC20 for IERC20;
using SafeERC20 for IWETH;

/*//////////////////////////////////////////////////////////////////////////
ETH Functions
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to force transfer ETH
/// @dev On failure to send the ETH, the ETH is converted to WETH and sent
/// @dev Care should be taken to always pass the proper WETH address that adheres to IWETH
/// @param recipient The recipient of the ETH
/// @param amount The amount of ETH to send
/// @param weth The WETH token address
function _safeTransferETH(address recipient, uint256 amount, address weth) internal {
(bool success,) = recipient.call{value: amount}("");
if (!success) {
IWETH token = IWETH(weth);
token.deposit{value: amount}();
token.safeTransfer(recipient, amount);
}
}

/*//////////////////////////////////////////////////////////////////////////
ERC-20 Functions
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to safely transfer ERC-20 tokens from the contract, without checking for token tax
/// @dev Does not check if the sender has enough balance as that is handled by the token contract
/// @dev Does not check for token tax as that could lock up funds in the contract
/// @dev Reverts on failure to transfer
/// @param recipient The recipient of the ERC-20 token
/// @param currency The address of the ERC-20 token
/// @param amount The amount of ERC-20 to send
function _safeTransferERC20(address recipient, address currency, uint256 amount) internal {
IERC20(currency).safeTransfer(recipient, amount);
}

/// @notice Function to safely transfer ERC-20 tokens from another address to a recipient
/// @dev Does not check if the sender has enough balance or allowance for this contract as that is handled by the token contract
/// @dev Reverts on failure to transfer
/// @dev Reverts if there is a token tax taken out
/// @param sender The sender of the tokens
/// @param recipient The recipient of the ERC-20 token
/// @param currency The address of the ERC-20 token
/// @param amount The amount of ERC-20 to send
function _safeTransferFromERC20(address sender, address recipient, address currency, uint256 amount) internal {
IERC20 token = IERC20(currency);
uint256 intialBalance = token.balanceOf(recipient);
token.safeTransferFrom(sender, recipient, amount);
uint256 finalBalance = token.balanceOf(recipient);
if (finalBalance - intialBalance < amount) revert InsufficentERC20Transfer();
}
}
2 changes: 1 addition & 1 deletion src/royalties/EIP2981TL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ error MaxRoyaltyError();
/// @dev follows EIP-2981 (https://eips.ethereum.org/EIPS/eip-2981)
/// @author transientlabs.xyz
/// https://github.com/Transient-Labs/tl-sol-tools
/// @custom:version 2.2.2
/// @custom:last-updated 2.2.2
abstract contract EIP2981TL is IEIP2981, ERC165 {
/*//////////////////////////////////////////////////////////////////////////
Royalty Struct
Expand Down
2 changes: 1 addition & 1 deletion src/upgradeable/access/OwnableAccessControlUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ error NotRoleOrOwner(bytes32 role);
/// @dev by default, only the owner can grant roles but by inheriting, but you
/// may allow other roles to grant roles by using the internal helper.
/// @author transientlabs.xyz
/// @custom:version 2.2.2
/// @custom:last-updated 2.2.2
abstract contract OwnableAccessControlUpgradeable is Initializable, OwnableUpgradeable {
/*//////////////////////////////////////////////////////////////////////////
State Variables
Expand Down
Loading

0 comments on commit 2476afa

Please sign in to comment.