# Payment Splitting ## How to calculate the withdrawable amount. The following is explanation the concept of calculating the amount of tokens a user can withdraw (=`payment`) using pseudocode in JS syntax. ref: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.6/contracts/finance/PaymentSplitter.sol ```javascript! const total_released_tokens = new Map([['TOKEN', 0]]); const released_tokens_of_user = new Map([['TOKEN', new Map([['USER', 0]])]]); function withdraw(token, user) { const Property = ERC20(property); const Token = ERC20(token); const total_received = Token.balanceOf(address(this)) + total_released_tokens.get(token); /* */ const released = released_tokens_of_user.get(token).get(user); const user_balance = Property.balanceOf(user); const total_supply = Property.totalSupply(); const payment = (total_received * user_balance) / total_supply - released; /* * (TRANSFER TOKENS TO THE USER HERE...) */ ERC20.safeTransfer(token, user, payment); /* * Update the global states. */ released_tokens_of_user.get(token).set(user, released + payment) total_released_tokens.set( token, total_released_tokens.get(token) + payment ); return payment; } ``` ## Cases The withdrawable amount is calculated from the latest share of Property. ### Case-1 |_timeline_|_event_|Alice|Bob|Locked Value| |---|---|---|---|---| |1||50%|50%|0| |2|+©100|50%|50%|100| |3|Alice 20% → Bob|30%|70%|100| |4|Alice withdraws<br/>Alice earns ©30|30%|70%|70| |5|Bob withdraws<br/>Bob earns ©70|30%|70%|0| ### Case-2 |_timeline_|_event_|Alice|Bob|Locked Value| |---|---|---|---|---| |1||50%|50%|0| |2|+©100|50%|50%|100| |3|Alice withdraws<br/>Alice earns ©50|50%|50%|50| |4|Alice 20% → Bob|30%|70%|50| |5|+©200|30%|70%|250| |6|Bob withdraws<br/>Bob earns ©210|30%|70%|40| |7|Alice withdraws<br/>Alice earns ©40|30%|70%|0| --- # Idea ref: https://github.com/dev-protocol/protocol-v2/pull/714 ### Vault side Add a call for updating `released` and a local state that records the user balance at the time of the last withdraw. ```diff const total_released_tokens = new Map([['TOKEN', 0]]); const released_tokens_of_user = new Map([['TOKEN', new Map([['USER', 0]])]]); + const last_balance_of_user_for_tokens = new Map([['TOKEN', new Map([['USER', 0]])]]); function withdraw(token, user) { const Property = ERC20(property); const Token = ERC20(token); const total_received = Token.balanceOf(address(this)) + total_released_tokens.get(token); + updateReleasedTokens(token, user, Property.balanceOf(user)); /* */ const released = released_tokens_of_user.get(token).get(user); const user_balance = Property.balanceOf(user); const total_supply = Property.totalSupply(); const payment = (total_received * user_balance) / total_supply - released; /* * (TRANSFER TOKENS TO THE USER HERE...) */ ERC20.safeTransfer(token, user, payment); /* * Update the global states. */ released_tokens_of_user.get(token).set(user, released + payment) total_released_tokens.set( token, total_released_tokens.get(token) + payment ); + last_balance_of_user_for_tokens[token][user] = user_balance; return payment; } ``` `updateReleasedTokens` updates the value of `released` by referencing `Withdraw.transferHistory` recursively until `last_balance_of_user_for_tokens` matches the latest balance. ```javascript! const released_tokens_addresses = new Map([['USER', new Set()]]); // mapping(address => EnumerableSet.AddressSet) const Withdraw = Withdraw(Registry(registry).registries('Withdraw')); function updateReleasedTokens(token, user, current_balance) { if (last_balance_of_user_for_tokens[token][user] === current_balance) { // No need to update it. return } const historyIndex = Withdraw.transferHistoryLengthOfRecipient(address(Property), user); let done = false; let i = historyIndex; let calculated = 0; while (!done || i >= 0) { const history = Withdraw.transferHistory( Withdraw.transferHistoryOfRecipientByIndex(i) ); if (!history.fill) { // This is always done on the history of the last 1 transfer, and other case, history.fill is true. history.amount = current_balance - history.preBalanceOfRecipient; } updateReleasedTokens(token, history.from, Property.balanceOf(history.from)); const released_tokens = released_tokens_of_user.get(token).get(history.from); const part_of_released = released_tokens * history.preBalanceOfSender / history.amount; released_tokens_of_user.get(token).set( user, part_of_released + released_tokens_of_user.get(token).get(user) ); i = i - 1; calculated = calculated + history.amount; done = last_balance_of_user_for_tokens[token][user] + calculated === current_balance; } } ```