Fix cumulative distribution calculation bug in SingleNocap

This commit is contained in:
lash 2022-05-27 11:10:31 +00:00
parent 127c67e665
commit a0557b35a0
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
11 changed files with 132 additions and 22 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,6 +38,15 @@ class DemurrageTokenSettings:
self.sink_address = None
def __str__(self):
return 'name {} demurrage level {} period minutes {} sink address {}'.format(
self.name,
self.demurrage_level,
self.period_minutes,
self.sink_address,
)
class DemurrageToken(ERC20):
__abi = {}
@ -445,14 +454,16 @@ class DemurrageToken(ERC20):
return o
def get_distribution_from_redistribution(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
def get_distribution_from_redistribution(self, contract_address, redistribution, redistribution_previous, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('getDistributionFromRedistribution')
enc.typ(ABIContractType.BYTES32)
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
enc.bytes32(redistribution_previous)
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)

View File

@ -44,6 +44,7 @@ class TestTokenDeploy:
self.settings.period_minutes = PERIOD
self.settings.sink_address = sink_address
self.sink_address = self.settings.sink_address
logg.debug('using demurrage token settings: {}'.format(self.settings))
o = block_latest()
self.start_block = rpc.do(o)
@ -203,7 +204,7 @@ class TestDemurrageCap(TestDemurrage):
class TestDemurrageUnit(TestDemurrage):
def setUp(self):
super(TestDemurrage, self).setUp()
super(TestDemurrageUnit, self).setUp()
self.tax_level = 50
self.period_seconds = 60

View File

@ -17,7 +17,12 @@ done
modes=(SingleCap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_period.py
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
done
modes=(SingleNocap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution.py
done
modes=(MultiCap SingleCap)

View File

@ -31,6 +31,78 @@ testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageDefault):
def test_redistribution_boundaries(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], supply)
self.rpc.do(o)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
logg.debug('balance before {} supply {}'.format(balance, supply))
self.backend.time_travel(self.start_time + self.period_seconds)
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
r = self.rpc.do(o)
oo = c.to_redistribution_supply(self.address, r, sender_address=self.accounts[0])
rr = self.rpc.do(oo)
oo = c.to_redistribution_demurrage_modifier(self.address, r, sender_address=self.accounts[0])
rr = self.rpc.do(oo)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.backend.time_travel(self.start_time + self.period_seconds * 2 + 1)
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.redistributions(self.address, 2, sender_address=self.accounts[0])
r = self.rpc.do(o)
oo = c.to_redistribution_supply(self.address, r, sender_address=self.accounts[0])
rr = self.rpc.do(oo)
oo = c.to_redistribution_demurrage_modifier(self.address, r, sender_address=self.accounts[0])
rr = self.rpc.do(oo)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionDemurrageModifier')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
o['params'].append(self.normalize(tx))
o['params'].append('latest')
o = j.finalize(o)
return o
def test_whole_is_parts(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)

View File

@ -31,8 +31,9 @@ testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageUnit):
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
def test_distribution(self):
def test_distribution_direct(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
@ -40,6 +41,8 @@ class TestRedistribution(TestDemurrageUnit):
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
logg.debug('demurrage {} supply {}'.format(demurrage))
o = c.get_distribution(self.address, supply, demurrage, sender_address=self.accounts[0])
r = self.rpc.do(o)
distribution = c.parse_get_distribution(r)
@ -51,17 +54,23 @@ class TestRedistribution(TestDemurrageUnit):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
#demurrage = (1 - (self.tax_level / 1000000)) * (10**38)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
demurrage_previous = (1 - (self.tax_level / 100000)) * (10**28)
demurrage = (1 - ((self.tax_level * 1.33) / 100000)) * (10**28)
logg.debug('demurrage then {} now {}'.format(demurrage_previous, demurrage))
supply = self.default_supply
o = c.to_redistribution(self.address, 0, demurrage, supply, 1, sender_address=self.accounts[0])
o = c.to_redistribution(self.address, 0, demurrage_previous, supply, 1, sender_address=self.accounts[0])
redistribution_previous = self.rpc.do(o)
o = c.to_redistribution(self.address, 0, demurrage, supply, 2, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.get_distribution_from_redistribution(self.address, redistribution, self.accounts[0])
o = c.get_distribution_from_redistribution(self.address, redistribution, redistribution_previous, self.accounts[0])
r = self.rpc.do(o)
distribution = c.parse_get_distribution(r)
expected_distribution = self.default_supply * (self.tax_level / 1000000)
logg.debug('distribution {} supply {}'.format(distribution, self.default_supply))
self.assert_within_lower(distribution, expected_distribution, 1000)

View File

@ -20,6 +20,8 @@ contract DemurrageTokenSingleCap {
// Cached demurrage amount, ppm with 38 digit resolution
uint128 public demurrageAmount;
uint256 public demurrageStart;
// Cached demurrage period; the period for which demurrageAmount was calculated
//uint128 public demurragePeriod;
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
@ -42,8 +44,11 @@ contract DemurrageTokenSingleCap {
// Implements ERC20
uint256 public totalSupply;
// Last executed period
uint256 public lastPeriod;
// Minimum amount of (demurraged) tokens an account must spend to participate in redistribution for a particular period
uint256 public minimumParticipantSpend;
//uint256 public minimumParticipantSpend;
// 128 bit resolution of the demurrage divisor
// (this constant x 1000000 is contained within 128 bits)
@ -113,7 +118,9 @@ contract DemurrageTokenSingleCap {
periodDuration = _periodMinutes * 60;
//demurrageAmount = 100000000000000000000000000000000000000 - _taxLevelMinute; // Represents 38 decimal places, same as resolutionFactor
//demurrageAmount = 100000000000000000000000000000000000000;
demurrageAmount = 10000000000000000000000000000;
//demurrageAmount = 10000000000000000000000000000;
demurrageAmount = uint128(nanoDivider) * 100;
demurrageStart = demurrageAmount;
//demurragePeriod = 1;
taxLevel = _taxLevelMinute; // Represents 38 decimal places
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
@ -121,7 +128,7 @@ contract DemurrageTokenSingleCap {
// Misc settings
sinkAddress = _defaultSinkAddress;
minimumParticipantSpend = 10 ** uint256(_decimals);
//minimumParticipantSpend = 10 ** uint256(_decimals);
}
@ -274,7 +281,8 @@ contract DemurrageTokenSingleCap {
bytes32 lastRedistribution;
uint256 currentPeriod;
lastRedistribution = redistributions[redistributions.length-1];
//lastRedistribution = redistributions[redistributions.length-1];
lastRedistribution = redistributions[lastPeriod];
currentPeriod = this.actualPeriod();
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
return bytes32(0x00);
@ -290,21 +298,25 @@ contract DemurrageTokenSingleCap {
return difference / resolutionFactor;
}
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
function getDistributionFromRedistribution(bytes32 _redistribution, bytes32 _redistributionPrevious) public returns (uint256) {
uint256 redistributionSupply;
uint256 redistributionDemurrage;
uint256 redistributionDemurragePrevious;
redistributionSupply = toRedistributionSupply(_redistribution);
redistributionDemurrage = toRedistributionDemurrageModifier(_redistribution);
redistributionDemurragePrevious = toRedistributionDemurrageModifier(_redistributionPrevious);
redistributionDemurrage = demurrageStart - (redistributionDemurragePrevious - redistributionDemurrage);
return getDistribution(redistributionSupply, redistributionDemurrage);
}
// Returns the amount sent to the sink address
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
function applyDefaultRedistribution(bytes32 _redistribution, bytes32 _redistributionPrevious) private returns (uint256) {
uint256 unit;
unit = getDistributionFromRedistribution(_redistribution);
unit = getDistributionFromRedistribution(_redistribution, _redistributionPrevious);
increaseBaseBalance(sinkAddress, toBaseAmount(unit));
lastPeriod += 1;
return unit;
}
@ -388,7 +400,7 @@ contract DemurrageTokenSingleCap {
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
redistributions.push(nextRedistribution);
applyDefaultRedistribution(nextRedistribution);
applyDefaultRedistribution(nextRedistribution, currentRedistribution);
emit Period(nextPeriod);
return true;
}