Skip to content

Commit 0138776

Browse files
committed
Add PaymentCombiner
1 parent a97ad78 commit 0138776

File tree

5 files changed

+396
-0
lines changed

5 files changed

+396
-0
lines changed

src/payments/IPaymentCombiner.sol

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.19;
3+
4+
interface IPaymentCombinerFunctions {
5+
/**
6+
* Get the address of the PaymentSplitter implementation.
7+
* @return implementationAddr The address of the PaymentSplitter implementation.
8+
*/
9+
function implementationAddress() external view returns (address implementationAddr);
10+
11+
/**
12+
* Creates a PaymentSplitter proxy.
13+
* @param payees The addresses of the payees
14+
* @param shares The number of shares each payee has
15+
* @return proxyAddr The address of the deployed proxy
16+
*/
17+
function deploy(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr);
18+
19+
/**
20+
* Computes the address of a proxy instance.
21+
* @param payees The addresses of the payees
22+
* @param shares The number of shares each payee has
23+
* @return proxyAddr The address of the proxy
24+
*/
25+
function determineAddress(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr);
26+
27+
/**
28+
* Get the list of Payment Splitters this payee is associated with.
29+
* @param payee The address of the payee
30+
* @return splitterAddrs The list of payments splitters
31+
*/
32+
function listPayeeSplitters(address payee) external view returns (address[] memory splitterAddrs);
33+
34+
/**
35+
* Get the list of pending shares for a payee.
36+
* @param payee The address of the payee
37+
* @param tokenAddr The address of the ERC-20 token. If the token address is 0x0, then the native token is used.
38+
* @return splitterAddrs The list of payments splitters with pending shares
39+
* @return pendingShares The list of pending shares
40+
* @dev The list includes zero balances. These should be removed before releasing shares.
41+
*/
42+
function listReleasable(address payee, address tokenAddr)
43+
external
44+
view
45+
returns (address[] memory splitterAddrs, uint256[] memory pendingShares);
46+
47+
/**
48+
* Release the pending shares for a payee.
49+
* @param payee The address of the payee
50+
* @param tokenAddr The address of the ERC-20 token. If the token address is 0x0, then the native token is used.
51+
* @param splitterAddrs The list of payments splitters to release shares from
52+
* @dev Use the listReleasableSplitters function to get the list of splitters and pending shares
53+
*/
54+
function release(address payable payee, address tokenAddr, address[] calldata splitterAddrs) external;
55+
}
56+
57+
interface IPaymentCombinerSignals {
58+
/**
59+
* Event emitted when a new proxy contract is deployed.
60+
* @param proxyAddr The address of the deployed proxy.
61+
*/
62+
event PaymentSplitterDeployed(address proxyAddr);
63+
}
64+
65+
interface IPaymentCombiner is IPaymentCombinerFunctions, IPaymentCombinerSignals {}

src/payments/PaymentCombiner.sol

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.19;
3+
4+
import {PaymentSplitter, IERC20Upgradeable} from "@0xsequence/contracts-library/payments/PaymentSplitter.sol";
5+
import {
6+
IPaymentCombiner, IPaymentCombinerFunctions
7+
} from "@0xsequence/contracts-library/payments/IPaymentCombiner.sol";
8+
import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol";
9+
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
10+
11+
/**
12+
* Deployer of Payment Splitter proxies.
13+
* @dev Unlike other factories in this library, payment splitters are unowned and not upgradeable.
14+
*/
15+
contract PaymentCombiner is IPaymentCombiner, IERC165 {
16+
using Clones for address;
17+
18+
address private immutable _IMPLEMENTATION;
19+
20+
mapping(address => address[]) private _payeeSplitters;
21+
22+
/**
23+
* Creates a Payment Splitter Factory.
24+
*/
25+
constructor() {
26+
_IMPLEMENTATION = address(new PaymentSplitter());
27+
}
28+
29+
/// @inheritdoc IPaymentCombinerFunctions
30+
function implementationAddress() external view returns (address) {
31+
return _IMPLEMENTATION;
32+
}
33+
34+
/// @inheritdoc IPaymentCombinerFunctions
35+
function deploy(address[] calldata payees, uint256[] calldata shares) external returns (address proxyAddr) {
36+
bytes32 salt = _determineSalt(payees, shares);
37+
proxyAddr = _IMPLEMENTATION.cloneDeterministic(salt);
38+
PaymentSplitter(payable(proxyAddr)).initialize(payees, shares);
39+
emit PaymentSplitterDeployed(proxyAddr);
40+
41+
// Add the payees to the list of payee splitters
42+
for (uint256 i = 0; i < payees.length; i++) {
43+
_payeeSplitters[payees[i]].push(proxyAddr);
44+
}
45+
46+
return proxyAddr;
47+
}
48+
49+
/// @inheritdoc IPaymentCombinerFunctions
50+
function determineAddress(address[] calldata payees, uint256[] calldata shares)
51+
external
52+
view
53+
returns (address proxyAddr)
54+
{
55+
bytes32 salt = _determineSalt(payees, shares);
56+
return _IMPLEMENTATION.predictDeterministicAddress(salt);
57+
}
58+
59+
/// @dev Computes the deployment salt for a Payment Splitter.
60+
function _determineSalt(address[] calldata payees, uint256[] calldata shares) internal pure returns (bytes32) {
61+
return keccak256(abi.encode(payees, shares));
62+
}
63+
64+
/// @inheritdoc IPaymentCombinerFunctions
65+
function listPayeeSplitters(address payee) external view returns (address[] memory splitterAddrs) {
66+
return _payeeSplitters[payee];
67+
}
68+
69+
/// @inheritdoc IPaymentCombinerFunctions
70+
function listReleasable(address payee, address tokenAddr)
71+
external
72+
view
73+
returns (address[] memory splitterAddrs, uint256[] memory pendingShares)
74+
{
75+
address[] memory payeeSplitters = _payeeSplitters[payee];
76+
uint256 len = payeeSplitters.length;
77+
uint256[] memory payeePendingShares = new uint256[](len);
78+
79+
if (tokenAddr == address(0)) {
80+
for (uint256 i = 0; i < len;) {
81+
payeePendingShares[i] = PaymentSplitter(payable(payeeSplitters[i])).releasable(payee);
82+
unchecked {
83+
i++;
84+
}
85+
}
86+
} else {
87+
for (uint256 i = 0; i < len;) {
88+
payeePendingShares[i] =
89+
PaymentSplitter(payable(payeeSplitters[i])).releasable(IERC20Upgradeable(tokenAddr), payee);
90+
unchecked {
91+
i++;
92+
}
93+
}
94+
}
95+
96+
return (payeeSplitters, payeePendingShares);
97+
}
98+
99+
/// @inheritdoc IPaymentCombinerFunctions
100+
function release(address payable payee, address tokenAddr, address[] calldata splitterAddrs) external {
101+
uint256 len = splitterAddrs.length;
102+
if (tokenAddr == address(0)) {
103+
for (uint256 i = 0; i < len;) {
104+
PaymentSplitter(payable(splitterAddrs[i])).release(payee);
105+
unchecked {
106+
i++;
107+
}
108+
}
109+
} else {
110+
for (uint256 i = 0; i < len;) {
111+
PaymentSplitter(payable(splitterAddrs[i])).release(IERC20Upgradeable(tokenAddr), payee);
112+
unchecked {
113+
i++;
114+
}
115+
}
116+
}
117+
}
118+
119+
/// @inheritdoc IERC165
120+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
121+
return type(IPaymentCombiner).interfaceId == interfaceId
122+
|| type(IPaymentCombinerFunctions).interfaceId == interfaceId || type(IERC165).interfaceId == interfaceId;
123+
}
124+
}

src/payments/PaymentSplitter.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.19;
3+
4+
import {
5+
PaymentSplitterUpgradeable,
6+
IERC20Upgradeable
7+
} from "@openzeppelin-upgradeable/contracts/finance/PaymentSplitterUpgradeable.sol";
8+
9+
contract PaymentSplitter is PaymentSplitterUpgradeable {
10+
/**
11+
* Initialize the PaymentSplitter contract.
12+
* @param payees The addresses of the payees
13+
* @param shares The number of shares each payee has
14+
* @dev This function should be called only once immediately after the contract is deployed.
15+
*/
16+
function initialize(address[] memory payees, uint256[] memory shares) public initializer {
17+
__PaymentSplitter_init(payees, shares);
18+
}
19+
}

test/TestHelper.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ abstract contract TestHelper is Test, Merkle {
4646
}
4747
}
4848

49+
function assumeNoDuplicates(address[] memory values) internal pure {
50+
for (uint256 i = 0; i < values.length; i++) {
51+
for (uint256 j = i + 1; j < values.length; j++) {
52+
vm.assume(values[i] != values[j]);
53+
}
54+
}
55+
}
56+
4957
function getMerkleParts(address[] memory allowlist, uint256 salt, uint256 leafIndex) internal pure returns (bytes32 root, bytes32[] memory proof) {
5058
bytes32[] memory leaves = new bytes32[](allowlist.length);
5159
for (uint256 i = 0; i < allowlist.length; i++) {

0 commit comments

Comments
 (0)