Guest User

LlamaPay.sol

a guest
Dec 7th, 2022
42
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.48 KB | None | 0 0
  1. //SPDX-License-Identifier: None
  2. pragma solidity ^0.8.0;
  3.  
  4. import {IERC20} from "./IERC20.sol";
  5. import {ERC20} from "./ERC20.sol";
  6. // import {BoringBatchable} from "./fork/BoringBatchable.sol";
  7.  
  8. interface Factory {
  9. function parameter() external view returns (address);
  10. }
  11.  
  12. interface IERC20WithDecimals {
  13. function decimals() external view returns (uint8);
  14. }
  15.  
  16. // All amountPerSec and all internal numbers use 20 decimals, these are converted to the right decimal on withdrawal/deposit
  17. // The reason for that is to minimize precision errors caused by integer math on tokens with low decimals (eg: USDC)
  18.  
  19. // Invariant through the whole contract: lastPayerUpdate[anyone] <= block.timestamp
  20. // Reason: timestamps can't go back in time (https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/consensus.go#L274 and block timestamp definition on ethereum's yellow paper)
  21. // and we always set lastPayerUpdate[anyone] either to the current block.timestamp or a value lower than it
  22. // We could use this to optimize subtractions and avoid an unneded safemath check there for some gas savings
  23. // However this is obscure enough that we are not sure if a future ethereum network upgrade might remove this assertion
  24. // or if an ethereum fork might remove that code and invalidate the condition, causing our deployment on that chain to be vulnerable
  25. // This is dangerous because if someone can make a timestamp go back into the past they could steal all the money
  26. // So we forgo these optimizations and instead enforce this condition.
  27.  
  28. // Another assumption is that all timestamps can fit in uint40, this will be true until year 231,800, so it's a safe assumption
  29.  
  30. contract LlamaPay {
  31. struct Payer {
  32. uint40 lastPayerUpdate;
  33. uint216 totalPaidPerSec; // uint216 is enough to hold 1M streams of 3e51 tokens/yr, which is enough
  34. }
  35.  
  36. mapping (bytes32 => uint) public streamToStart;
  37. mapping (address => Payer) public payers;
  38. mapping (address => uint) public balances; // could be packed together with lastPayerUpdate but gains are not high
  39. IERC20 public token;
  40. uint public DECIMALS_DIVISOR;
  41.  
  42. event StreamCreated(address from, address to, uint216 amountPerSec, bytes32 streamId);
  43. event StreamCreatedWithReason(address from, address to, uint216 amountPerSec, bytes32 streamId, string reason);
  44. event StreamCancelled(address from, address to, uint216 amountPerSec, bytes32 streamId);
  45. event StreamPaused(address from, address to, uint216 amountPerSec, bytes32 streamId);
  46. event StreamModified(address from, address oldTo, uint216 oldAmountPerSec, bytes32 oldStreamId, address to, uint216 amountPerSec, bytes32 newStreamId);
  47. event Withdraw(address from, address to, uint216 amountPerSec, bytes32 streamId, uint amount);
  48. event PayerDeposit(address from, uint amount);
  49. event PayerWithdraw(address from, uint amount);
  50.  
  51. constructor(){
  52. token = IERC20(Factory(msg.sender).parameter());
  53. uint8 tokenDecimals = IERC20WithDecimals(address(token)).decimals();
  54. DECIMALS_DIVISOR = 10**(20 - tokenDecimals);
  55. }
  56.  
  57. function getStreamId(address from, address to, uint216 amountPerSec) public pure returns (bytes32){
  58. uint256 f = uint256(from);
  59. uint256 t = uint256(to);
  60.  
  61. return keccak256(abi.encodePacked(f, t, amountPerSec));
  62. }
  63.  
  64. function _createStream(address to, uint216 amountPerSec) internal returns (bytes32 streamId){
  65. streamId = getStreamId(msg.sender, to, amountPerSec);
  66. require(amountPerSec > 0, "amountPerSec can't be 0");
  67. require(streamToStart[streamId] == 0, "stream already exists");
  68. streamToStart[streamId] = block.timestamp;
  69.  
  70. Payer storage payer = payers[msg.sender];
  71. uint totalPaid;
  72. uint delta = block.timestamp - payer.lastPayerUpdate;
  73. unchecked {
  74. totalPaid = delta * uint(payer.totalPaidPerSec);
  75. }
  76. balances[msg.sender] -= totalPaid; // implicit check that balance >= totalPaid, can't create a new stream unless there's no debt
  77.  
  78. payer.lastPayerUpdate = uint40(block.timestamp);
  79. payer.totalPaidPerSec += amountPerSec;
  80.  
  81. // checking that no overflow will ever happen on totalPaidPerSec is important because if there's an overflow later:
  82. // - if we don't have overflow checks -> it would be possible to steal money from other people
  83. // - if there are overflow checks -> money will be stuck forever as all txs (from payees of the same payer) will revert
  84. // which can be used to rug employees and make them unable to withdraw their earnings
  85. // Thus it's extremely important that no user is allowed to enter any value that later on could trigger an overflow.
  86. // We implicitly prevent this here because amountPerSec/totalPaidPerSec is uint216 and is only ever multiplied by timestamps
  87. // which will always fit in a uint40. Thus the result of the multiplication will always fit inside a uint256 and never overflow
  88. // This however introduces a new invariant: the only operations that can be done with amountPerSec/totalPaidPerSec are muls against timestamps
  89. // and we need to make sure they happen in uint256 contexts, not any other
  90. }
  91.  
  92. function createStream(address to, uint216 amountPerSec) public {
  93. bytes32 streamId = _createStream(to, amountPerSec);
  94. emit StreamCreated(msg.sender, to, amountPerSec, streamId);
  95. }
  96.  
  97. function createStreamWithReason(address to, uint216 amountPerSec, string calldata reason) public {
  98. bytes32 streamId = _createStream(to, amountPerSec);
  99. emit StreamCreatedWithReason(msg.sender, to, amountPerSec, streamId, reason);
  100. }
  101.  
  102. /*
  103. proof that lastUpdate < block.timestamp:
  104.  
  105. let's start by assuming the opposite, that lastUpdate > block.timestamp, and then we'll prove that this is impossible
  106. lastUpdate > block.timestamp
  107. -> timePaid = lastUpdate - lastPayerUpdate[from] > block.timestamp - lastPayerUpdate[from] = payerDelta
  108. -> timePaid > payerDelta
  109. -> payerBalance = timePaid * totalPaidPerSec[from] > payerDelta * totalPaidPerSec[from] = totalPayerPayment
  110. -> payerBalance > totalPayerPayment
  111. but this last statement is impossible because if it were true we'd have gone into the first if branch!
  112. */
  113. /*
  114. proof that totalPaidPerSec[from] != 0:
  115.  
  116. totalPaidPerSec[from] is a sum of uint that are different from zero (since we test that on createStream())
  117. and we test that there's at least one stream active with `streamToStart[streamId] != 0`,
  118. so it's a sum of one or more elements that are higher than zero, thus it can never be zero
  119. */
  120.  
  121. // Make it possible to withdraw on behalf of others, important for people that don't have a metamask wallet (eg: cex address, trustwallet...)
  122. function _withdraw(address from, address to, uint216 amountPerSec) private returns (uint40 lastUpdate, bytes32 streamId, uint amountToTransfer) {
  123. streamId = getStreamId(from, to, amountPerSec);
  124. require(streamToStart[streamId] != 0, "stream doesn't exist");
  125.  
  126. Payer storage payer = payers[from];
  127. uint totalPayerPayment;
  128. uint payerDelta = block.timestamp - payer.lastPayerUpdate;
  129. unchecked{
  130. totalPayerPayment = payerDelta * uint(payer.totalPaidPerSec);
  131. }
  132. uint payerBalance = balances[from];
  133. if(payerBalance >= totalPayerPayment){
  134. unchecked {
  135. balances[from] = payerBalance - totalPayerPayment;
  136. }
  137. lastUpdate = uint40(block.timestamp);
  138. } else {
  139. // invariant: totalPaidPerSec[from] != 0
  140. unchecked {
  141. uint timePaid = payerBalance/uint(payer.totalPaidPerSec);
  142. lastUpdate = uint40(payer.lastPayerUpdate + timePaid);
  143. // invariant: lastUpdate < block.timestamp (we need to maintain it)
  144. balances[from] = payerBalance % uint(payer.totalPaidPerSec);
  145. }
  146. }
  147. uint delta = lastUpdate - streamToStart[streamId]; // Could use unchecked here too I think
  148. unchecked {
  149. // We push transfers to be done outside this function and at the end of public functions to avoid reentrancy exploits
  150. amountToTransfer = (delta*uint(amountPerSec))/DECIMALS_DIVISOR;
  151. }
  152. emit Withdraw(from, to, amountPerSec, streamId, amountToTransfer);
  153. }
  154.  
  155. // Copy of _withdraw that is view-only and returns how much can be withdrawn from a stream, purely for convenience on frontend
  156. // No need to review since this does nothing
  157. function withdrawable(address from, address to, uint216 amountPerSec) external view returns (uint withdrawableAmount, uint lastUpdate, uint owed) {
  158. bytes32 streamId = getStreamId(from, to, amountPerSec);
  159. require(streamToStart[streamId] != 0, "stream doesn't exist");
  160.  
  161. Payer storage payer = payers[from];
  162. uint totalPayerPayment;
  163. uint payerDelta = block.timestamp - payer.lastPayerUpdate;
  164. unchecked{
  165. totalPayerPayment = payerDelta * uint(payer.totalPaidPerSec);
  166. }
  167. uint payerBalance = balances[from];
  168. if(payerBalance >= totalPayerPayment){
  169. lastUpdate = block.timestamp;
  170. } else {
  171. unchecked {
  172. uint timePaid = payerBalance/uint(payer.totalPaidPerSec);
  173. lastUpdate = payer.lastPayerUpdate + timePaid;
  174. }
  175. }
  176. uint delta = lastUpdate - streamToStart[streamId];
  177. withdrawableAmount = (delta*uint(amountPerSec))/DECIMALS_DIVISOR;
  178. owed = ((block.timestamp - lastUpdate)*uint(amountPerSec))/DECIMALS_DIVISOR;
  179. }
  180.  
  181. function withdraw(address from, address to, uint216 amountPerSec) external {
  182. (uint40 lastUpdate, bytes32 streamId, uint amountToTransfer) = _withdraw(from, to, amountPerSec);
  183. streamToStart[streamId] = lastUpdate;
  184. payers[from].lastPayerUpdate = lastUpdate;
  185. token.transfer(to, amountToTransfer);
  186. }
  187.  
  188. function _cancelStream(address to, uint216 amountPerSec) internal returns (bytes32 streamId) {
  189. uint40 lastUpdate; uint amountToTransfer;
  190. (lastUpdate, streamId, amountToTransfer) = _withdraw(msg.sender, to, amountPerSec);
  191. streamToStart[streamId] = 0;
  192. Payer storage payer = payers[msg.sender];
  193. unchecked{
  194. // totalPaidPerSec is a sum of items which include amountPerSec, so totalPaidPerSec >= amountPerSec
  195. payer.totalPaidPerSec -= amountPerSec;
  196. }
  197. payer.lastPayerUpdate = lastUpdate;
  198. token.transfer(to, amountToTransfer);
  199. }
  200.  
  201. function cancelStream(address to, uint216 amountPerSec) public {
  202. bytes32 streamId = _cancelStream(to, amountPerSec);
  203. emit StreamCancelled(msg.sender, to, amountPerSec, streamId);
  204. }
  205.  
  206. function pauseStream(address to, uint216 amountPerSec) public {
  207. bytes32 streamId = _cancelStream(to, amountPerSec);
  208. emit StreamPaused(msg.sender, to, amountPerSec, streamId);
  209. }
  210.  
  211. function modifyStream(address oldTo, uint216 oldAmountPerSec, address to, uint216 amountPerSec) external {
  212. // Can be optimized but I don't think extra complexity is worth it
  213. bytes32 oldStreamId = _cancelStream(oldTo, oldAmountPerSec);
  214. bytes32 newStreamId = _createStream(to, amountPerSec);
  215. emit StreamModified(msg.sender, oldTo, oldAmountPerSec, oldStreamId, to, amountPerSec, newStreamId);
  216. }
  217.  
  218. function deposit(uint amount) public {
  219. balances[msg.sender] += amount * DECIMALS_DIVISOR;
  220. token.transferFrom(msg.sender, address(this), amount);
  221. emit PayerDeposit(msg.sender, amount);
  222. }
  223.  
  224. function depositAndCreate(uint amountToDeposit, address to, uint216 amountPerSec) external {
  225. deposit(amountToDeposit);
  226. createStream(to, amountPerSec);
  227. }
  228.  
  229. function depositAndCreateWithReason(uint amountToDeposit, address to, uint216 amountPerSec, string calldata reason) external {
  230. deposit(amountToDeposit);
  231. createStreamWithReason(to, amountPerSec, reason);
  232. }
  233.  
  234. function withdrawPayer(uint amount) public {
  235. Payer storage payer = payers[msg.sender];
  236. balances[msg.sender] -= amount; // implicit check that balance > amount
  237. uint delta = block.timestamp - payer.lastPayerUpdate;
  238. unchecked {
  239. require(balances[msg.sender] >= delta*uint(payer.totalPaidPerSec), "pls no rug");
  240. uint tokenAmount = amount/DECIMALS_DIVISOR;
  241. token.transfer(msg.sender, tokenAmount);
  242. emit PayerWithdraw(msg.sender, tokenAmount);
  243. }
  244. }
  245.  
  246. function withdrawPayerAll() external {
  247. Payer storage payer = payers[msg.sender];
  248. unchecked {
  249. uint delta = block.timestamp - payer.lastPayerUpdate;
  250. // Just helper function, nothing happens if number is wrong
  251. // If there's an overflow it's just equivalent to calling withdrawPayer() directly with a big number
  252. withdrawPayer(balances[msg.sender]-delta*uint(payer.totalPaidPerSec));
  253. }
  254. }
  255.  
  256. function getPayerBalance(address payerAddress) external view returns (int) {
  257. Payer storage payer = payers[payerAddress];
  258. int balance = int(balances[payerAddress]);
  259. uint delta = block.timestamp - payer.lastPayerUpdate;
  260. return (balance - int(delta*uint(payer.totalPaidPerSec)))/int(DECIMALS_DIVISOR);
  261. }
  262. }
  263.  
Add Comment
Please, Sign In to add comment