Quick Solidity walk through
The following code was taken from Open Zeppelin library:
- It's the de facto library to use when developing smart contracts in Solidity.
- It has tons of base contracts to use and awesome utilities.
- It's open source and community audited.
- Battle tested in production.
- And has been built in ♥️ 🇦🇷.
We will be navigating contract that:
- "Allows to split Ether payments among a group of accounts."
- "The split can be in equal parts or in any other arbitrary proportion."
Disclaimer: this is a quick first dive and by no means is extensive or covers all the different features, design patterns or security best practices that need to be considered when developing in Solidity.
Solidity version and imports
Whenever we start a solidity file we need to declare the Solidity version we are going to use:
- Solidity has evolved quite fast so new features were added making previous versions not to be compatible
- Remember everything is compiled to EVM bytecode so unless there is an upgrade, the compile code is compatible.
Also libraries (like OZ) can be used helping quite a lot the development process.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Comments
Developing easy to follow, well tested and with proper comments is extremely important as most of the time code that can be abused is written in solidity.
Comments should be written in Ethereum Natural Language Specification Format (NatSpec)
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Contract declaration
Contracts resemble to OOP:
- Multiple inheritance
- Interfaces, Abstract Contracts
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Events
Events can be emitted when executing transactions.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Storage Variables
Variables can be placed in the storage, thus making their state persistent.
There are several types that can be used:
- Unsigned integers (multiple lengths)
- Mappings (maps)
- Addresses
- Arrays (fixed and variable length)
- Bytes
- Strings (fixed and variable length)
- Structs
- Booleans
- Contracts
- Enums
Visibility (public, private, internal)
⚠️ Even if something is private in the context of the EVM it will be public for somebody else observing the blockchain.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Constructor
Can be used to set some storage values when deploying the contracts
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Error handling
Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the state in the current call (and all its sub-calls) and flags an error to the caller.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Control structures
There is: if
, else
, while
, do
, for
, break
, continue
, return
, with the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of try/catch-statements.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Functions
Functions can be declared:
public
: can be invoked by users, other contracts and from other functions in the same contractexternal
: only by users or another contractinternal
: same or child contractsprivate
: only the owning contract
Functions can also be:
view
: reads the state but cannot change itpure
: does not involve any state change- by default they can read and write the state
Remember that:
- Just reading the blockchain (without a transaction) is free.
- Invoking a transaction costs ETH.
- Different operations have different cost.
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Receiving ETH
Functions must be declared payable
to be able to receive ETH. The amount transferred can be accessed by a global object named msg
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Contracts interaction
Contracts can call other contracts
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Solidity version and imports
Whenever we start a solidity file we need to declare the Solidity version we are going to use:
- Solidity has evolved quite fast so new features were added making previous versions not to be compatible
- Remember everything is compiled to EVM bytecode so unless there is an upgrade, the compile code is compatible.
Also libraries (like OZ) can be used helping quite a lot the development process.
Comments
Developing easy to follow, well tested and with proper comments is extremely important as most of the time code that can be abused is written in solidity.
Comments should be written in Ethereum Natural Language Specification Format (NatSpec)
Contract declaration
Contracts resemble to OOP:
- Multiple inheritance
- Interfaces, Abstract Contracts
Events
Events can be emitted when executing transactions.
Storage Variables
Variables can be placed in the storage, thus making their state persistent.
There are several types that can be used:
- Unsigned integers (multiple lengths)
- Mappings (maps)
- Addresses
- Arrays (fixed and variable length)
- Bytes
- Strings (fixed and variable length)
- Structs
- Booleans
- Contracts
- Enums
Visibility (public, private, internal)
⚠️ Even if something is private in the context of the EVM it will be public for somebody else observing the blockchain.
Constructor
Can be used to set some storage values when deploying the contracts
Error handling
Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the state in the current call (and all its sub-calls) and flags an error to the caller.
Control structures
There is: if
, else
, while
, do
, for
, break
, continue
, return
, with the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of try/catch-statements.
Functions
Functions can be declared:
public
: can be invoked by users, other contracts and from other functions in the same contractexternal
: only by users or another contractinternal
: same or child contractsprivate
: only the owning contract
Functions can also be:
view
: reads the state but cannot change itpure
: does not involve any state change- by default they can read and write the state
Remember that:
- Just reading the blockchain (without a transaction) is free.
- Invoking a transaction costs ETH.
- Different operations have different cost.
Receiving ETH
Functions must be declared payable
to be able to receive ETH. The amount transferred can be accessed by a global object named msg
Contracts interaction
Contracts can call other contracts
_214// SPDX-License-Identifier: MIT_214// OpenZeppelin Contracts (last updated v4.7.0) (finance/PaymentSplitter.sol)_214_214pragma solidity ^0.8.0;_214_214import "../token/ERC20/utils/SafeERC20.sol";_214import "../utils/Address.sol";_214import "../utils/Context.sol";_214_214/**_214 * @title PaymentSplitter_214 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware_214 * that the Ether will be split in this way, since it is handled transparently by the contract._214 *_214 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each_214 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim_214 * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the_214 * time of contract deployment and can't be updated thereafter._214 *_214 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the_214 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}_214 * function._214 *_214 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and_214 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you_214 * to run tests before sending real value to this contract._214 */_214contract PaymentSplitter is Context {_214 event PayeeAdded(address account, uint256 shares);_214 event PaymentReleased(address to, uint256 amount);_214 event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);_214 event PaymentReceived(address from, uint256 amount);_214_214 uint256 private _totalShares;_214 uint256 private _totalReleased;_214_214 mapping(address => uint256) private _shares;_214 mapping(address => uint256) private _released;_214 address[] private _payees;_214_214 mapping(IERC20 => uint256) private _erc20TotalReleased;_214 mapping(IERC20 => mapping(address => uint256)) private _erc20Released;_214_214 /**_214 * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at_214 * the matching position in the `shares` array._214 *_214 * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no_214 * duplicates in `payees`._214 */_214 constructor(address[] memory payees, uint256[] memory shares_) payable {_214 require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");_214 require(payees.length > 0, "PaymentSplitter: no payees");_214_214 for (uint256 i = 0; i < payees.length; i++) {_214 _addPayee(payees[i], shares_[i]);_214 }_214 }_214_214 /**_214 * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully_214 * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the_214 * reliability of the events, and not the actual splitting of Ether._214 *_214 * To learn more about this see the Solidity documentation for_214 * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback_214 * functions]._214 */_214 receive() external payable virtual {_214 emit PaymentReceived(_msgSender(), msg.value);_214 }_214_214 /**_214 * @dev Getter for the total shares held by payees._214 */_214 function totalShares() public view returns (uint256) {_214 return _totalShares;_214 }_214_214 /**_214 * @dev Getter for the total amount of Ether already released._214 */_214 function totalReleased() public view returns (uint256) {_214 return _totalReleased;_214 }_214_214 /**_214 * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20_214 * contract._214 */_214 function totalReleased(IERC20 token) public view returns (uint256) {_214 return _erc20TotalReleased[token];_214 }_214_214 /**_214 * @dev Getter for the amount of shares held by an account._214 */_214 function shares(address account) public view returns (uint256) {_214 return _shares[account];_214 }_214_214 /**_214 * @dev Getter for the amount of Ether already released to a payee._214 */_214 function released(address account) public view returns (uint256) {_214 return _released[account];_214 }_214_214 /**_214 * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an_214 * IERC20 contract._214 */_214 function released(IERC20 token, address account) public view returns (uint256) {_214 return _erc20Released[token][account];_214 }_214_214 /**_214 * @dev Getter for the address of the payee number `index`._214 */_214 function payee(uint256 index) public view returns (address) {_214 return _payees[index];_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable Ether._214 */_214 function releasable(address account) public view returns (uint256) {_214 uint256 totalReceived = address(this).balance + totalReleased();_214 return _pendingPayment(account, totalReceived, released(account));_214 }_214_214 /**_214 * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an_214 * IERC20 contract._214 */_214 function releasable(IERC20 token, address account) public view returns (uint256) {_214 uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);_214 return _pendingPayment(account, totalReceived, released(token, account));_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the_214 * total shares and their previous withdrawals._214 */_214 function release(address payable account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _totalReleased is the sum of all values in _released._214 // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow._214 _totalReleased += payment;_214 unchecked {_214 _released[account] += payment;_214 }_214_214 Address.sendValue(account, payment);_214 emit PaymentReleased(account, payment);_214 }_214_214 /**_214 * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their_214 * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20_214 * contract._214 */_214 function release(IERC20 token, address account) public virtual {_214 require(_shares[account] > 0, "PaymentSplitter: account has no shares");_214_214 uint256 payment = releasable(token, account);_214_214 require(payment != 0, "PaymentSplitter: account is not due payment");_214_214 // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]._214 // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"_214 // cannot overflow._214 _erc20TotalReleased[token] += payment;_214 unchecked {_214 _erc20Released[token][account] += payment;_214 }_214_214 SafeERC20.safeTransfer(token, account, payment);_214 emit ERC20PaymentReleased(token, account, payment);_214 }_214_214 /**_214 * @dev internal logic for computing the pending payment of an `account` given the token historical balances and_214 * already released amounts._214 */_214 function _pendingPayment(_214 address account,_214 uint256 totalReceived,_214 uint256 alreadyReleased_214 ) private view returns (uint256) {_214 return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;_214 }_214_214 /**_214 * @dev Add a new payee to the contract._214 * @param account The address of the payee to add._214 * @param shares_ The number of shares owned by the payee._214 */_214 function _addPayee(address account, uint256 shares_) private {_214 require(account != address(0), "PaymentSplitter: account is the zero address");_214 require(shares_ > 0, "PaymentSplitter: shares are 0");_214 require(_shares[account] == 0, "PaymentSplitter: account already has shares");_214_214 _payees.push(account);_214 _shares[account] = shares_;_214 _totalShares = _totalShares + shares_;_214 emit PayeeAdded(account, shares_);_214 }_214}
Where to go next?
Again, Solidity is a complex language that requires attention to details:
- Security
- Execution cost / performance
The following links might be a good start for you: