Skip to content

Commit

Permalink
Add PaymentCombiner
Browse files Browse the repository at this point in the history
  • Loading branch information
ScreamingHawk committed Aug 22, 2024
1 parent a97ad78 commit 0138776
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/payments/IPaymentCombiner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IPaymentCombinerFunctions {
/**
* Get the address of the PaymentSplitter implementation.
* @return implementationAddr The address of the PaymentSplitter implementation.
*/
function implementationAddress() external view returns (address implementationAddr);

/**
* Creates a PaymentSplitter proxy.
* @param payees The addresses of the payees
* @param shares The number of shares each payee has
* @return proxyAddr The address of the deployed proxy
*/
function deploy(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr);

/**
* Computes the address of a proxy instance.
* @param payees The addresses of the payees
* @param shares The number of shares each payee has
* @return proxyAddr The address of the proxy
*/
function determineAddress(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr);

/**
* Get the list of Payment Splitters this payee is associated with.
* @param payee The address of the payee
* @return splitterAddrs The list of payments splitters
*/
function listPayeeSplitters(address payee) external view returns (address[] memory splitterAddrs);

/**
* Get the list of pending shares for a payee.
* @param payee The address of the payee
* @param tokenAddr The address of the ERC-20 token. If the token address is 0x0, then the native token is used.
* @return splitterAddrs The list of payments splitters with pending shares
* @return pendingShares The list of pending shares
* @dev The list includes zero balances. These should be removed before releasing shares.
*/
function listReleasable(address payee, address tokenAddr)
external
view
returns (address[] memory splitterAddrs, uint256[] memory pendingShares);

/**
* Release the pending shares for a payee.
* @param payee The address of the payee
* @param tokenAddr The address of the ERC-20 token. If the token address is 0x0, then the native token is used.
* @param splitterAddrs The list of payments splitters to release shares from
* @dev Use the listReleasableSplitters function to get the list of splitters and pending shares
*/
function release(address payable payee, address tokenAddr, address[] calldata splitterAddrs) external;
}

interface IPaymentCombinerSignals {
/**
* Event emitted when a new proxy contract is deployed.
* @param proxyAddr The address of the deployed proxy.
*/
event PaymentSplitterDeployed(address proxyAddr);
}

interface IPaymentCombiner is IPaymentCombinerFunctions, IPaymentCombinerSignals {}
124 changes: 124 additions & 0 deletions src/payments/PaymentCombiner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {PaymentSplitter, IERC20Upgradeable} from "@0xsequence/contracts-library/payments/PaymentSplitter.sol";
import {
IPaymentCombiner, IPaymentCombinerFunctions
} from "@0xsequence/contracts-library/payments/IPaymentCombiner.sol";
import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

/**
* Deployer of Payment Splitter proxies.
* @dev Unlike other factories in this library, payment splitters are unowned and not upgradeable.
*/
contract PaymentCombiner is IPaymentCombiner, IERC165 {
using Clones for address;

address private immutable _IMPLEMENTATION;

Check warning on line 18 in src/payments/PaymentCombiner.sol

View workflow job for this annotation

GitHub Actions / Solidity lint

Variable name must be in mixedCase

mapping(address => address[]) private _payeeSplitters;

/**
* Creates a Payment Splitter Factory.
*/
constructor() {
_IMPLEMENTATION = address(new PaymentSplitter());
}

/// @inheritdoc IPaymentCombinerFunctions
function implementationAddress() external view returns (address) {
return _IMPLEMENTATION;
}

/// @inheritdoc IPaymentCombinerFunctions
function deploy(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr) {
bytes32 salt = _determineSalt(payees, shares);
proxyAddr = _IMPLEMENTATION.cloneDeterministic(salt);
PaymentSplitter(payable(proxyAddr)).initialize(payees, shares);
emit PaymentSplitterDeployed(proxyAddr);

// Add the payees to the list of payee splitters
for (uint256 i = 0; i < payees.length; i++) {
_payeeSplitters[payees[i]].push(proxyAddr);
}

return proxyAddr;
}

/// @inheritdoc IPaymentCombinerFunctions
function determineAddress(address[] calldata payees, uint256[] calldata shares)
external
view
returns (address proxyAddr)
{
bytes32 salt = _determineSalt(payees, shares);
return _IMPLEMENTATION.predictDeterministicAddress(salt);
}

/// @dev Computes the deployment salt for a Payment Splitter.
function _determineSalt(address[] calldata payees, uint256[] calldata shares) internal pure returns (bytes32) {
return keccak256(abi.encode(payees, shares));
}

/// @inheritdoc IPaymentCombinerFunctions
function listPayeeSplitters(address payee) external view returns (address[] memory splitterAddrs) {
return _payeeSplitters[payee];
}

/// @inheritdoc IPaymentCombinerFunctions
function listReleasable(address payee, address tokenAddr)
external
view
returns (address[] memory splitterAddrs, uint256[] memory pendingShares)
{
address[] memory payeeSplitters = _payeeSplitters[payee];
uint256 len = payeeSplitters.length;
uint256[] memory payeePendingShares = new uint256[](len);

if (tokenAddr == address(0)) {
for (uint256 i = 0; i < len;) {
payeePendingShares[i] = PaymentSplitter(payable(payeeSplitters[i])).releasable(payee);
unchecked {
i++;
}
}
} else {
for (uint256 i = 0; i < len;) {
payeePendingShares[i] =
PaymentSplitter(payable(payeeSplitters[i])).releasable(IERC20Upgradeable(tokenAddr), payee);
unchecked {
i++;
}
}
}

return (payeeSplitters, payeePendingShares);
}

/// @inheritdoc IPaymentCombinerFunctions
function release(address payable payee, address tokenAddr, address[] calldata splitterAddrs) external {
uint256 len = splitterAddrs.length;
if (tokenAddr == address(0)) {
for (uint256 i = 0; i < len;) {
PaymentSplitter(payable(splitterAddrs[i])).release(payee);
unchecked {
i++;
}
}
} else {
for (uint256 i = 0; i < len;) {
PaymentSplitter(payable(splitterAddrs[i])).release(IERC20Upgradeable(tokenAddr), payee);
unchecked {
i++;
}
}
}
}

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return type(IPaymentCombiner).interfaceId == interfaceId
|| type(IPaymentCombinerFunctions).interfaceId == interfaceId || type(IERC165).interfaceId == interfaceId;
}
}
19 changes: 19 additions & 0 deletions src/payments/PaymentSplitter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {
PaymentSplitterUpgradeable,
IERC20Upgradeable
} from "@openzeppelin-upgradeable/contracts/finance/PaymentSplitterUpgradeable.sol";

contract PaymentSplitter is PaymentSplitterUpgradeable {
/**
* Initialize the PaymentSplitter contract.
* @param payees The addresses of the payees
* @param shares The number of shares each payee has
* @dev This function should be called only once immediately after the contract is deployed.
*/
function initialize(address[] memory payees, uint256[] memory shares) public initializer {
__PaymentSplitter_init(payees, shares);
}
}
8 changes: 8 additions & 0 deletions test/TestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ abstract contract TestHelper is Test, Merkle {
}
}

function assumeNoDuplicates(address[] memory values) internal pure {
for (uint256 i = 0; i < values.length; i++) {
for (uint256 j = i + 1; j < values.length; j++) {
vm.assume(values[i] != values[j]);
}
}
}

function getMerkleParts(address[] memory allowlist, uint256 salt, uint256 leafIndex) internal pure returns (bytes32 root, bytes32[] memory proof) {
bytes32[] memory leaves = new bytes32[](allowlist.length);
for (uint256 i = 0; i < allowlist.length; i++) {
Expand Down
Loading

0 comments on commit 0138776

Please sign in to comment.