From a2c15f7493fb392f1eb6a313b971bb6d2be5d211 Mon Sep 17 00:00:00 2001 From: Red Swan Date: Mon, 25 Dec 2023 22:25:36 -0700 Subject: [PATCH 1/7] Add rebalancing token --- .vscode/settings.json | 3 ++ readme.md | 2 +- src/Rebalancing.sol | 115 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 src/Rebalancing.sol diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a016705 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "solidity.compileUsingRemoteVersion": "v0.8.20+commit.a1b79de6" +} \ No newline at end of file diff --git a/readme.md b/readme.md index 103d5fe..5262013 100644 --- a/readme.md +++ b/readme.md @@ -94,7 +94,7 @@ In the case of Ampleforth, some Balancer and Uniswap pools are special cased to pool's cached balances are atomically updated as part of the rebase procedure ([details](https://www.ampltalk.org/app/forum/technology-development-17/topic/supported-dex-pools-61/)). -*example*: TODO: implement a rebasing token +*example*: [Rebalancing.sol](./src/Rebalancing.sol) ## Upgradable Tokens diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol new file mode 100644 index 0000000..2a10d1e --- /dev/null +++ b/src/Rebalancing.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: NONE +pragma solidity ^0.8.20; + +/// @title An ERC20 who's totalSupply and user balances are always increasing +/// @notice The idea is that these tokens represent votes in a governance process +/// and holders' votes gain value over time. +contract Rebalancer { + + uint256 private constant ONE = 1e18; + uint256 public constant decimals = 18; + uint256 public immutable voteGrowthStartTime; + uint256 public immutable voteGrowthPerSecond; + uint256 public immutable totalVoteShares; + + mapping(address => uint256) public userVoteShares; + mapping(address => mapping(address => uint256)) public allowance; + + /// @notice Constructs the contract. It gives all tokens to the calling address for them + /// to distribute. + /// @param _totalVoteShares the total number of initial tokens to mint, becomes the total + /// number of total shares as they gain value + /// @param _voteGrowthPerYear an 18 decimal percentage that represents the simple interest + /// a vote accrues over a year. I.e. 6% growth should be 0.06 ether + constructor(uint256 _totalVoteShares, uint256 _voteGrowthPerYear) { + uint256 secondsPerYear = 60*60*24*365; // leap years be damned + voteGrowthPerSecond = _voteGrowthPerYear / secondsPerYear; + voteGrowthStartTime = block.timestamp; + userVoteShares[msg.sender] = _totalVoteShares; + totalVoteShares = _totalVoteShares; + } + + /// @notice Calculate the total growth of shares over time + /// @dev Since all tokens are minted at construction, we can skip having to + /// checkpoint each users "interest index" whenever they receive shares. + /// @return the vote "multiplier" that when multiplied by shares, gives + /// the total number of votes it is worth + function computeVoteGrowth() view internal returns(uint256) { + uint256 timeElapsed = block.timestamp - voteGrowthStartTime; + return ONE + (voteGrowthPerSecond * timeElapsed); + } + + /// @notice Returns the number of votes a vote share is worth + /// @param _shares the number of which we would like to convert to tokens (i.e. votes) + /// @return the number of tokens (i.e. votes) the shares are worth + function getPresentTokenCount(uint256 _shares) view internal returns(uint256){ + return _shares * computeVoteGrowth() / ONE; + } + + /// @notice Returns the total supply of tokens at this very moment + /// @return the total number of tokens in circulation + function totalSupply() view public returns (uint256){ + return getPresentTokenCount(totalVoteShares); + } + + /// @notice Returns the current token count of an address + /// @param _owner The address we would like to see the token count of + /// @return The total number of tokens owned by the specified address + function balanceOf(address _owner) public view returns (uint256){ + return getPresentTokenCount(userVoteShares[_owner]); + } + + /// @notice Transfer an amount of tokens to another user + /// @dev Because we store vote shares instead of votes, we need to convert the + /// specified _value into a share amount before we can modify user balances + /// @param _to The address to transfer vote tokens to + /// @param _value The amount of vote tokens to transfer + /// @return Whether the transfer succeeded or not + function transfer(address _to, uint256 _value) public returns (bool){ + // we check if their shares are worth that much + if(balanceOf(msg.sender) < _value){ + return false; + } + + uint256 valueAsShares = (_value * ONE) / computeVoteGrowth(); + userVoteShares[_to] += valueAsShares; + userVoteShares[msg.sender] -= valueAsShares; + + return true; + } + + /// @notice Transfer tokens from one address to another if allowed + /// @dev We always check for allowance. msg.sender should use transfer instead + /// @param _from The address to transfer from + /// @param _to The address to transfer to + /// @param _value The amount of vote tokens to transfer + /// @return Whether or not the transfer succeeded. Does not specify what went wrong if it did. + function transferFrom(address _from, address _to, uint256 _value) public returns (bool){ + // we always check for allowance, msg.sender should use transfer instead + if(allowance[_from][_to] < _value ){ + return false; + } + if(balanceOf(_from) < _value){ + return false; + } + + uint256 valueAsShares = (_value * ONE) / computeVoteGrowth(); + userVoteShares[_to] += valueAsShares; + userVoteShares[_from] -= valueAsShares; + allowance[_from][_to] -= _value; + + return true; + } + + /// @notice Allow another user to transfer a specified amount of tokens away from the caller + /// @dev We do not enforce any behavior here. For example, users do not have to set + /// an allowance to zero first before being able to change a users allowance. + /// @param _spender The address for which the caller will allow token access + /// @param _value The amount of vote tokens the caller will allow the _spender to transfer + /// @return Whether the approval succeeded + function approve(address _spender, uint256 _value) public returns (bool){ + allowance[msg.sender][_spender] = _value; + return true; + } + +} \ No newline at end of file From c17dba570b4157a5aed238752ec1c811adbf3847 Mon Sep 17 00:00:00 2001 From: Red Swan Date: Mon, 29 Jan 2024 23:23:39 -0700 Subject: [PATCH 2/7] Delete .vscode/settings.json Woops! --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a016705..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "solidity.compileUsingRemoteVersion": "v0.8.20+commit.a1b79de6" -} \ No newline at end of file From dc20b0a37cf368d5cfebe706d7211ac57ba5a1be Mon Sep 17 00:00:00 2001 From: Red Swan Date: Thu, 15 Feb 2024 12:33:54 -0700 Subject: [PATCH 3/7] Add approve/transfer events --- src/Rebalancing.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol index 2a10d1e..4dac000 100644 --- a/src/Rebalancing.sol +++ b/src/Rebalancing.sol @@ -15,6 +15,12 @@ contract Rebalancer { mapping(address => uint256) public userVoteShares; mapping(address => mapping(address => uint256)) public allowance; + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + + /// @notice Constructs the contract. It gives all tokens to the calling address for them /// to distribute. /// @param _totalVoteShares the total number of initial tokens to mint, becomes the total @@ -26,6 +32,7 @@ contract Rebalancer { voteGrowthPerSecond = _voteGrowthPerYear / secondsPerYear; voteGrowthStartTime = block.timestamp; userVoteShares[msg.sender] = _totalVoteShares; + emit Transfer(0, msg.sender, _totalVoteShares); totalVoteShares = _totalVoteShares; } @@ -61,7 +68,8 @@ contract Rebalancer { /// @notice Transfer an amount of tokens to another user /// @dev Because we store vote shares instead of votes, we need to convert the - /// specified _value into a share amount before we can modify user balances + /// specified _value into a share amount before we can modify user balances + /// @dev Emit `Transfer` event to register token transfer from sender (`msg.sender`) /// @param _to The address to transfer vote tokens to /// @param _value The amount of vote tokens to transfer /// @return Whether the transfer succeeded or not @@ -74,7 +82,7 @@ contract Rebalancer { uint256 valueAsShares = (_value * ONE) / computeVoteGrowth(); userVoteShares[_to] += valueAsShares; userVoteShares[msg.sender] -= valueAsShares; - + emit Transfer(msg.sender, _to, _value); return true; } @@ -103,12 +111,14 @@ contract Rebalancer { /// @notice Allow another user to transfer a specified amount of tokens away from the caller /// @dev We do not enforce any behavior here. For example, users do not have to set - /// an allowance to zero first before being able to change a users allowance. + /// an allowance to zero first before being able to change a users allowance. + /// @dev Emit `Approval` event to register approval of sender address (`msg.sender`) /// @param _spender The address for which the caller will allow token access /// @param _value The amount of vote tokens the caller will allow the _spender to transfer /// @return Whether the approval succeeded function approve(address _spender, uint256 _value) public returns (bool){ allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); return true; } From 3cff535fafde1e2137958ae5fb11ca3da083e87d Mon Sep 17 00:00:00 2001 From: Red Swan Date: Thu, 15 Feb 2024 12:41:44 -0700 Subject: [PATCH 4/7] Add mapping key/value names Also changes `allowance` variable to `allowances` --- src/Rebalancing.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol index 4dac000..428f676 100644 --- a/src/Rebalancing.sol +++ b/src/Rebalancing.sol @@ -12,8 +12,8 @@ contract Rebalancer { uint256 public immutable voteGrowthPerSecond; uint256 public immutable totalVoteShares; - mapping(address => uint256) public userVoteShares; - mapping(address => mapping(address => uint256)) public allowance; + mapping(address account => uint256 shares) public userVoteShares; + mapping(address account => mapping(address spender => uint256)) public allowances; event Transfer(address indexed _from, address indexed _to, uint256 _value); @@ -94,7 +94,7 @@ contract Rebalancer { /// @return Whether or not the transfer succeeded. Does not specify what went wrong if it did. function transferFrom(address _from, address _to, uint256 _value) public returns (bool){ // we always check for allowance, msg.sender should use transfer instead - if(allowance[_from][_to] < _value ){ + if(allowances[_from][_to] < _value ){ return false; } if(balanceOf(_from) < _value){ @@ -104,7 +104,7 @@ contract Rebalancer { uint256 valueAsShares = (_value * ONE) / computeVoteGrowth(); userVoteShares[_to] += valueAsShares; userVoteShares[_from] -= valueAsShares; - allowance[_from][_to] -= _value; + allowances[_from][_to] -= _value; return true; } @@ -117,7 +117,7 @@ contract Rebalancer { /// @param _value The amount of vote tokens the caller will allow the _spender to transfer /// @return Whether the approval succeeded function approve(address _spender, uint256 _value) public returns (bool){ - allowance[msg.sender][_spender] = _value; + allowances[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } From 371345c94f3061f74a93411ac2b2022ebb672bf9 Mon Sep 17 00:00:00 2001 From: Red Swan Date: Thu, 15 Feb 2024 12:44:58 -0700 Subject: [PATCH 5/7] Fix address --- src/Rebalancing.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol index 428f676..6e421dc 100644 --- a/src/Rebalancing.sol +++ b/src/Rebalancing.sol @@ -32,7 +32,7 @@ contract Rebalancer { voteGrowthPerSecond = _voteGrowthPerYear / secondsPerYear; voteGrowthStartTime = block.timestamp; userVoteShares[msg.sender] = _totalVoteShares; - emit Transfer(0, msg.sender, _totalVoteShares); + emit Transfer(address(0x00), msg.sender, _totalVoteShares); totalVoteShares = _totalVoteShares; } From 2e2a73cddbfe407b3f8e278c52eee9ab25e83bc7 Mon Sep 17 00:00:00 2001 From: Red Swan Date: Thu, 15 Feb 2024 12:45:49 -0700 Subject: [PATCH 6/7] Change 0x00 to 0 --- src/Rebalancing.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol index 6e421dc..2a06d30 100644 --- a/src/Rebalancing.sol +++ b/src/Rebalancing.sol @@ -32,7 +32,7 @@ contract Rebalancer { voteGrowthPerSecond = _voteGrowthPerYear / secondsPerYear; voteGrowthStartTime = block.timestamp; userVoteShares[msg.sender] = _totalVoteShares; - emit Transfer(address(0x00), msg.sender, _totalVoteShares); + emit Transfer(address(0), msg.sender, _totalVoteShares); totalVoteShares = _totalVoteShares; } From 7b04736e5e5e85e7ad954765f6f426c1b56307ec Mon Sep 17 00:00:00 2001 From: Red Swan Date: Thu, 15 Feb 2024 12:46:50 -0700 Subject: [PATCH 7/7] Change allowances variable name back to allowance I forgot I did this to get around writing an allowance function and had changed it to allowances to make the name more accurate. Woops. --- src/Rebalancing.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rebalancing.sol b/src/Rebalancing.sol index 2a06d30..c0b6dee 100644 --- a/src/Rebalancing.sol +++ b/src/Rebalancing.sol @@ -13,7 +13,7 @@ contract Rebalancer { uint256 public immutable totalVoteShares; mapping(address account => uint256 shares) public userVoteShares; - mapping(address account => mapping(address spender => uint256)) public allowances; + mapping(address account => mapping(address spender => uint256)) public allowance; event Transfer(address indexed _from, address indexed _to, uint256 _value); @@ -94,7 +94,7 @@ contract Rebalancer { /// @return Whether or not the transfer succeeded. Does not specify what went wrong if it did. function transferFrom(address _from, address _to, uint256 _value) public returns (bool){ // we always check for allowance, msg.sender should use transfer instead - if(allowances[_from][_to] < _value ){ + if(allowance[_from][_to] < _value ){ return false; } if(balanceOf(_from) < _value){ @@ -104,7 +104,7 @@ contract Rebalancer { uint256 valueAsShares = (_value * ONE) / computeVoteGrowth(); userVoteShares[_to] += valueAsShares; userVoteShares[_from] -= valueAsShares; - allowances[_from][_to] -= _value; + allowance[_from][_to] -= _value; return true; } @@ -117,7 +117,7 @@ contract Rebalancer { /// @param _value The amount of vote tokens the caller will allow the _spender to transfer /// @return Whether the approval succeeded function approve(address _spender, uint256 _value) public returns (bool){ - allowances[msg.sender][_spender] = _value; + allowance[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; }