From 60b8e57dfd223e5cec2b96a0a7f11ce748c643a4 Mon Sep 17 00:00:00 2001 From: aggre Date: Thu, 22 Aug 2024 17:51:48 +0900 Subject: [PATCH] Create idea.md --- idea.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 idea.md diff --git a/idea.md b/idea.md new file mode 100644 index 00000000..7921fc07 --- /dev/null +++ b/idea.md @@ -0,0 +1,164 @@ +# 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
Alice earns ©30|30%|70%|70| +|5|Bob withdraws
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
Alice earns ©50|50%|50%|50| +|4|Alice 20% → Bob|30%|70%|50| +|5|+©200|30%|70%|250| +|6|Bob withdraws
Bob earns ©210|30%|70%|40| +|7|Alice withdraws
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; + } +} +``` +