Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New/transfer dividends #287

Merged
merged 5 commits into from
Apr 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions contracts/examples/dao/DAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract DAO is VentureEth, Democratic {
/**
* @dev Fallback function. Required when collecting ether dividends from ventures.
*/
receive() external virtual payable {}
receive() external virtual override payable {}

/**
* @notice To be called during the first investment round.
Expand All @@ -66,7 +66,7 @@ contract DAO is VentureEth, Democratic {
* @param investment The ether to invest in the venture.
*/
function investVenture(
address venture,
address payable venture,
uint256 investment
) public virtual onlyProposal {
ventures.add(venture);
Expand All @@ -79,7 +79,7 @@ contract DAO is VentureEth, Democratic {
* @param venture The address of the VentureEth contract to retrieve tokens from.
*/
function retrieveVentureTokens(
address venture
address payable venture
) public virtual {
VentureEth(venture).claim();
}
Expand All @@ -89,7 +89,7 @@ contract DAO is VentureEth, Democratic {
* @param venture The address of the VentureEth contract from which to cancel the investment.
*/
function cancelVenture(
address venture
address payable venture
) public virtual onlyProposal {
VentureEth(venture).cancelInvestment();
ventures.remove(venture);
Expand All @@ -100,7 +100,7 @@ contract DAO is VentureEth, Democratic {
* @param venture The venture to claim dividends from.
*/
function claimDividendsFromVenture(
address venture
address payable venture
) public virtual returns(uint256) {
return VentureEth(venture).claimDividends();
}
Expand Down
7 changes: 7 additions & 0 deletions contracts/test/token/TestERC20DividendableEth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ contract TestERC20DividendableEth is ERC20DividendableEth, ERC20Burnable
function testReleaseDividends(uint256 amount) public virtual {
_releaseDividends(amount);
}

/// @dev There is some bug. If you override a function you seem to need to override it in all derived contracts.
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal override(ERC20, ERC20DividendableEth)
{
super._beforeTokenTransfer(from, to, amount);
}
}
90 changes: 52 additions & 38 deletions contracts/token/ERC20DividendableEth.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "./ERC20MintableDetailed.sol";
import "../math/DecimalMath.sol";
import "../utils/SafeCast.sol";


/**
Expand All @@ -11,65 +11,79 @@ import "../math/DecimalMath.sol";
* @notice This contract was implemented from algorithms proposed by Nick Johnson here: https://medium.com/@weka/dividend-bearing-tokens-on-ethereum-42d01c710657
*/
contract ERC20DividendableEth is ERC20MintableDetailed {

using SafeMath for uint256;
using DecimalMath for int256;
using DecimalMath for uint256;
using SafeCast for int256;
using SafeCast for uint256;

int256 public dividendsPerToken;
mapping(address => int256) private claimedDPT;

uint256 public dividendsPerToken; // This is a decimal number
mapping(address => uint256) public lastDPT; // These are decimal numbers
constructor(string memory name, string memory symbol, uint8 decimals)
ERC20MintableDetailed(name, symbol, decimals) public
{}

constructor(
string memory name,
string memory symbol,
uint8 decimals
) ERC20MintableDetailed(name, symbol, decimals) public {}
/// @dev Receive function
receive() external virtual payable {}

/**
* @notice Send ether to this function in orther to disburse dividends
*/
function releaseDividends() external virtual payable {
/// @dev Send ether to this function in order to release dividends
function releaseDividends()
external virtual payable
{
_releaseDividends(msg.value);
}

/**
* @dev Function to update the account of the sender
* @notice Will revert if account need not be updated
*/
function claimDividends() public virtual returns(uint256) {
/// @dev Function to update the account of the sender
/// @notice Will revert if account need not be updated
function claimDividends()
public virtual returns(uint256)
{
return _claimDividends(msg.sender);
}

/**
* @dev Release an `amount` of ether in the contract as dividends.
*/
function _releaseDividends(uint256 amount) internal {
/// @dev Release an `amount` of ether in the contract as dividends.
function _releaseDividends(uint256 amount)
internal
{
require(address(this).balance >= amount, "Not enough funds.");
// Wei amounts are already decimals.
uint256 releasedDPT = amount.divd(this.totalSupply());
int256 releasedDPT = amount.divd(this.totalSupply()).toInt();
dividendsPerToken = dividendsPerToken.addd(releasedDPT);
claimedDPT[address(0)] = dividendsPerToken; // Mint tokens at DPT
}

/**
* @dev Transfer owed dividends to its account.
*/
/// @dev Transfer owed dividends to its account.
function _claimDividends(address payable account)
internal
returns(uint256)
internal returns(uint256)
{
uint256 owing = _dividendsOwing(account);
require(owing > 0, "Account need not be updated now.");
account.transfer(owing);
lastDPT[account] = dividendsPerToken;
claimedDPT[account] = dividendsPerToken;
return owing;
}

/**
* @dev Internal function to compute dividends owing to an account
* @param account The account for which to compute the dividends
*/
function _dividendsOwing(address account) internal view returns(uint256) {
uint256 owedDPT = dividendsPerToken.subd(lastDPT[account]);
return this.balanceOf(account).muld(owedDPT);
/// @dev Internal function to compute dividends owing to an account
/// @param account The account for which to compute the dividends
function _dividendsOwing(address account)
internal view returns(uint256)
{
int256 owedDPT = dividendsPerToken.subd(claimedDPT[account]);
return owedDPT.toUint().muld(this.balanceOf(account));
}

/// @dev Add to the adjustment DPT the weighted average between the recipient's balance DPT, and the transfer tokens DPT
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal virtual override
{
// If burning, do nothing
if (to == address(0)) return;

// If transferring to an empty account, reset its claimed DTP
if (this.balanceOf(to) == 0) delete claimedDPT[to];

int256 weight = amount.divd(this.balanceOf(to).addd(amount)).toInt();
int256 differentialDPT = claimedDPT[from].subd(claimedDPT[to]);
int256 weightedDPT = differentialDPT.muld(weight);
claimedDPT[to] = claimedDPT[to].addd(weightedDPT);
}
}
17 changes: 7 additions & 10 deletions contracts/token/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,14 @@ It is a contract that implements the `IERC20MintableDetailed` interface.

It is a `ERC20` token contract that is endowed with some rather dividendable qualities.

1. Anyone can send `ether` to the contract at any time using `increasePool`. That amount of `ether` will be added to a dividend pool.
1. Anyone can send `ether` to the contract at any time using the `receive` function. That amount of `ether` will be added to a dividend pool.

2. Any token holder can draw their fair share of `ether` from the dividend pool according to the amount of tokens they hold. To do this they must call the `updateAccount` function.
2. The contract has an internal `_releaseDividends` function that will earmark a portion of the `ether` in the contract to be claimed by token holders proportionally to their holdings.

#### Notes
1. Changes in the token supply will affect any dividend distribution events. Any ongoing distribution events for which the contract has received the `ether` before the total supply change are unaffected.
3. Any token holder can claim their share of `ether` dividends calling the `claimDividends` function.

2. In order to be useful in practice, the contract has to be customized by inheriting from a mintable standard implementation, for example, openzeppelin's `ERC20Mintable` contract, in this way:
4. A token holder can transfer tokens while having dividends available for claiming. In that case, only the recipient can claim the share of dividends related to the tokens transferred.

```
contract MyERC20DividendableEth is ERC20DividendableEth, ERC20Mintable {
// here goes your fantasy
}
```
5. Minted tokens don't give any right to claim dividends from prior events.

6. Burning tokens while having dividends available for claiming proportionally reduces the dividends that can be claimed.
31 changes: 31 additions & 0 deletions contracts/utils/SafeCast.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
pragma solidity ^0.6.0;


/// @dev Implements safe casting between int256 and uint256
/// @author Alberto Cuesta Cañada
library SafeCast {

/// @dev Maximum value that can be represented in an int256
function maxInt256() internal pure returns(int256) {
// solium-disable-next-line max-len
return 57896044618658097711785492504343953926634992332820282019728792003956564819967;
}

/// @dev Safe casting from int256 to uint256
function toUint(int256 x) internal pure returns(uint256) {
require(
x >= 0,
"Cannot cast negative signed integer to unsigned integer."
);
return uint256(x);
}

/// @dev Safe casting from uint256 to int256
function toInt(uint256 x) internal pure returns(int256) {
require(
x <= toUint(maxInt256()),
"Cannot cast overflowing unsigned integer to signed integer."
alcueca marked this conversation as resolved.
Show resolved Hide resolved
);
return int256(x);
}
}
57 changes: 48 additions & 9 deletions test/token/ERC20DividendableEth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,56 @@ contract('ERC20DividendableEth', (accounts) => {
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends2);
});
});

/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends can be claimed after minting tokens', async () => {
await erc20dividendableEth.releaseDividends({ from: user1, value: releasedDividends.toString()});
await erc20dividendableEth.mint(account2, balance1.add(balance2));
await erc20dividendableEth.releaseDividends({ from: user1, value: releasedDividends.toString()});
BN(await erc20dividendableEth.claimDividends.call({ from: account1 }))
/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends per token are adjusted downwards after minting tokens', async () => {
await erc20dividendableEth.mint(account2, balance1.add(balance2));
BN(await erc20dividendableEth.claimDividends.call({ from: account1 }))
.should.be.bignumber.equal(claimedDividends1);
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends2);
});

/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends per token remain constant after burning tokens', async () => {
await erc20dividendableEth.burn(balance2.div(new BN('2')), { from: account2 });
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends2.div(new BN('2')));
});

/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends can be claimed after transferring tokens', async () => {
await erc20dividendableEth.transfer(account2, balance1, { from: account1 });
await expectRevert(erc20dividendableEth.claimDividends({ from: account1 }), 'Account need not be updated now.');
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends1.add(claimedDividends2));
});

/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends per token are adjusted downwards after transferring tokens', async () => {
await erc20dividendableEth.claimDividends({ from: account1 })
await erc20dividendableEth.transfer(account2, balance1, { from: account1 });
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends2);
});

/**
* @test {ERC20DividendableEth#claimDividends}
*/
it('dividends per token are adjusted upwards after transferring tokens', async () => {
await erc20dividendableEth.claimDividends({ from: account2 })
await erc20dividendableEth.transfer(account2, balance1, { from: account1 });
BN(await erc20dividendableEth.claimDividends.call({ from: account2 }))
.should.be.bignumber.equal(claimedDividends1);
});
});

/**
Expand Down