Introduce sim example

This commit is contained in:
nolash 2021-06-06 09:34:18 +02:00
parent 2f5bb63f9a
commit c69d115965
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
9 changed files with 323 additions and 24 deletions

File diff suppressed because one or more lines are too long

View File

@ -97,6 +97,7 @@ class DemurrageTokenSimulation:
self.period = 1 self.period = 1
self.period_txs = [] self.period_txs = []
self.period_tx_limit = self.period_seconds - 1 self.period_tx_limit = self.period_seconds - 1
self.sink_address = settings.sink_address
logg.info('intialized at block {} timestamp {} period {} demurrage level {} sink address {} (first address in keystore)'.format( logg.info('intialized at block {} timestamp {} period {} demurrage level {} sink address {} (first address in keystore)'.format(
self.last_block, self.last_block,
@ -126,6 +127,32 @@ class DemurrageTokenSimulation:
logg.debug('tx {} block {} index {} verified'.format(tx_hash, self.last_block, rcpt['transaction_index'])) logg.debug('tx {} block {} index {} verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
def get_now(self):
o = block_latest()
r = self.rpc.do(o)
o = block_by_number(r, include_tx=False)
r = self.rpc.do(o)
return r['timestamp']
def get_minutes(self):
t = self.get_now()
return int((t - self.start_timestamp) / 60)
def get_start(self):
return self.start_timestamp
def get_period(self):
return self.period
def get_demurrage_modifier(self):
o = self.caller_contract.demurrage_amount(self.address, sender_address=self.caller_address)
r = self.rpc.do(o)
return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 38))
def from_units(self, v): def from_units(self, v):
return v * (10 ** self.decimals) return v * (10 ** self.decimals)
@ -136,9 +163,10 @@ class DemurrageTokenSimulation:
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value) (tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value)
self.rpc.do(o) self.rpc.do(o)
self.next_block() self.__next_block()
self.__check_tx(tx_hash) self.__check_tx(tx_hash)
self.period_txs.append(tx_hash) self.period_txs.append(tx_hash)
logg.info('mint {} tokens to {} - {}'.format(value, recipient, tx_hash))
return tx_hash return tx_hash
@ -147,9 +175,10 @@ class DemurrageTokenSimulation:
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.transfer(self.address, sender, recipient, value) (tx_hash, o) = c.transfer(self.address, sender, recipient, value)
self.rpc.do(o) self.rpc.do(o)
self.next_block() self.__next_block()
self.__check_tx(tx_hash) self.__check_tx(tx_hash)
self.period_txs.append(tx_hash) self.period_txs.append(tx_hash)
logg.info('transfer {} tokens from {} to {} - {}'.format(value, sender, recipient, tx_hash))
return tx_hash return tx_hash
@ -163,42 +192,51 @@ class DemurrageTokenSimulation:
return self.caller_contract.parse_balance_of(r) return self.caller_contract.parse_balance_of(r)
def next_block(self): def __next_block(self):
hsh = self.eth_helper.mine_block() hsh = self.eth_helper.mine_block()
o = block_by_hash(hsh) o = block_by_hash(hsh)
r = self.rpc.do(o) r = self.rpc.do(o)
logg.info('now at block {} timestamp {}'.format(r['number'], r['timestamp']))
for tx_hash in r['transactions']:
o = receipt(tx_hash)
rcpt = self.rpc.do(o)
if rcpt['status'] == 0:
raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
logg.debug('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
logg.debug('now at block {} timestamp {}'.format(r['number'], r['timestamp']))
def next(self): def next(self):
target_timestamp = self.start_timestamp + (self.period * self.period_seconds) - 1 target_timestamp = self.start_timestamp + (self.period * self.period_seconds)
logg.debug('warping to {}, {} from start'.format(target_timestamp, target_timestamp - self.start_timestamp)) logg.debug('warping to {}, {} from start'.format(target_timestamp, target_timestamp - self.start_timestamp))
self.last_timestamp = target_timestamp self.last_timestamp = target_timestamp
self.eth_helper.time_travel(self.last_timestamp) self.eth_helper.time_travel(self.last_timestamp)
self.next_block() self.__next_block()
o = block_by_number(self.last_block) o = block_by_number(self.last_block)
r = self.rpc.do(o) r = self.rpc.do(o)
self.last_block = r['number'] self.last_block = r['number']
block_base = self.last_block block_base = self.last_block
# for tx_hash in r['transactions']:
# o = receipt(tx_hash)
# rcpt = self.rpc.do(o)
# if rcpt['status'] == 0:
# raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
# logg.info('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc) nonce_oracle = RPCNonceOracle(self.accounts[2], conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0]) (tx_hash, o) = c.change_period(self.address, self.accounts[2])
self.rpc.do(o) self.rpc.do(o)
self.next_block()
(tx_hash, o) = c.change_period(self.address, self.accounts[0]) for actor in self.actors:
nonce_oracle = RPCNonceOracle(actor, conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.apply_redistribution_on_account(self.address, actor, actor)
self.rpc.do(o)
nonce_oracle = RPCNonceOracle(self.sink_address, conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.apply_redistribution_on_account(self.address, self.sink_address, self.sink_address)
self.rpc.do(o) self.rpc.do(o)
self.next_block() self.__next_block()
o = block_latest() o = block_latest()
self.last_block = self.rpc.do(o) self.last_block = self.rpc.do(o)
@ -212,7 +250,7 @@ class DemurrageTokenSimulation:
raise RuntimeError('demurrage step failed on block {}'.format(self.last_block)) raise RuntimeError('demurrage step failed on block {}'.format(self.last_block))
self.last_timestamp = r['timestamp'] self.last_timestamp = r['timestamp']
logg.info('next concludes at block {} timestamp {}, {} after start'.format(self.last_block, self.last_timestamp, self.last_timestamp - self.start_timestamp)) logg.debug('next concludes at block {} timestamp {}, {} after start'.format(self.last_block, self.last_timestamp, self.last_timestamp - self.start_timestamp))
self.period += 1 self.period += 1
self.period_txs = [] self.period_txs = []

View File

@ -223,6 +223,51 @@ class DemurrageToken(ERC20):
return o return o
def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionParticipants')
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')
return o
def to_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionSupply')
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')
return o
def to_redistribution_demurrage_modifier(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_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')
return o
def base_balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS): def base_balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS):
o = jsonrpc_template() o = jsonrpc_template()
o['method'] = 'eth_call' o['method'] = 'eth_call'
@ -323,6 +368,11 @@ class DemurrageToken(ERC20):
return abi_decode_single(ABIContractType.UINT256, v) return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_to_redistribution_item(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod @classmethod
def parse_supply_cap(self, v): def parse_supply_cap(self, v):
return abi_decode_single(ABIContractType.UINT256, v) return abi_decode_single(ABIContractType.UINT256, v)

76
python/examples/sim.py Normal file
View File

@ -0,0 +1,76 @@
# standard imports
import logging
# local imports
from erc20_demurrage_token import DemurrageTokenSettings
from erc20_demurrage_token.sim import DemurrageTokenSimulation
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
decay_per_minute = 0.000050105908373373 # equals approx 2% per month
# parameters for simulation object
settings = DemurrageTokenSettings()
settings.name = 'Simulated Demurrage Token'
settings.symbol = 'SIM'
settings.decimals = 6
settings.demurrage_level = int(decay_per_minute*(10**40))
settings.period_minutes = 10800 # 1 week in minutes
chain = 'evm:foochain:42'
cap = sim.from_units(10 ** 12) # 1 tn token units, with 6 decimal places
# instantiate simulation
sim = DemurrageTokenSimulation(chain, settings, redistribute=True, cap=cap, actors=10)
# name the usual suspects
alice = sim.actors[0]
bob = sim.actors[1]
carol = sim.actors[2]
# mint and transfer (every single action advances one block, and one second in time)
sim.mint(alice, sim.from_units(100)) # 10000000 tokens
sim.mint(bob, sim.from_units(100))
sim.transfer(alice, carol, sim.from_units(50))
# check that balances have been updated
assert sim.balance(alice) == sim.from_units(50)
assert sim.balance(bob) == sim.from_units(100)
assert sim.balance(carol) == sim.from_units(50)
# advance to next redistribution period
sim.next()
# inspect balances
print('alice balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(alice), sim.balance(alice, base=True)))
print('bob balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(bob), sim.balance(bob, base=True)))
print('carol balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(carol), sim.balance(carol, base=True)))
# get times
minutes = sim.get_minutes()
start = sim.get_now()
timestamp = sim.get_start()
period = sim.get_period()
print('start {} now {} period {} minutes passed {}'.format(start, timestamp, period, minutes))
contract_demurrage = 1 - sim.get_demurrage_modifier() # demurrage in percent (float)
frontend_demurrage = ((1 - decay_per_minute) ** minutes / 100) # corresponding demurrage modifier (float)
demurrage_delta = contract_demurrage - frontend_demurrage # difference between demurrage in contract and demurrage calculated in frontend
alice_checksum = 50000000 - (50000000 * frontend_demurrage) + (200000000 * frontend_demurrage) # alice's balance calculated with frontend demurrage
print("""alice frontend balance {}
alice contract balance {}
frontend demurrage {}
contract demurrage {}
demurrage delta {}""".format(
alice_checksum,
sim.balance(alice),
frontend_demurrage,
contract_demurrage,
demurrage_delta),
)
balance_sum = sim.balance(alice) + sim.balance(bob) + sim.balance(carol)
print('sum of contract demurraged balances {}'.format(balance_sum))

View File

@ -163,3 +163,51 @@ class TestDemurrageCap(TestDemurrage):
self.deploy(c, self.mode) self.deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode)) logg.info('deployed with mode {}'.format(self.mode))
class TestDemurrageReal(TestDemurrage):
def setUp(self):
super(TestDemurrage, self).setUp()
self.tax_level = int(0.000050105908373373*(10**40))
self.period = 10800
self.period_seconds = self.period * 60
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
self.settings = DemurrageTokenSettings()
self.settings.name = 'Foo Token'
self.settings.symbol = 'FOO'
self.settings.decimals = 6
self.settings.demurrage_level = self.tax_level
self.settings.period_minutes = 10800
self.settings.sink_address = self.accounts[9]
self.sink_address = self.settings.sink_address
o = block_latest()
self.start_block = self.rpc.do(o)
o = block_by_number(self.start_block, include_tx=False)
r = self.rpc.do(o)
try:
self.start_time = int(r['timestamp'], 16)
except TypeError:
self.start_time = int(r['timestamp'])
self.default_supply = 1000000000000
self.default_supply_cap = int(self.default_supply * 10)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
if self.mode == None:
self.mode = 'MultiNocap'
logg.debug('executing test setup default mode {}'.format(self.mode))
self.deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode))

View File

@ -60,7 +60,7 @@ class TestSim(unittest.TestCase):
self.sim.next() self.sim.next()
balance = self.sim.balance(self.sim.actors[0]) balance = self.sim.balance(self.sim.actors[0])
self.assertEqual(balance, 89995500) self.assertEqual(balance, 90005520)
balance = self.sim.balance(self.sim.actors[1]) balance = self.sim.balance(self.sim.actors[1])
self.assertEqual(balance, 99995000) self.assertEqual(balance, 99995000)

View File

@ -26,7 +26,6 @@ logg = logging.getLogger()
testdir = os.path.dirname(__file__) testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageDefault): class TestRedistribution(TestDemurrageDefault):
def test_debug_periods(self): def test_debug_periods(self):
@ -261,3 +260,5 @@ class TestRedistribution(TestDemurrageDefault):
self.assertEqual(spender_actual_balance, spender_new_decayed_balance) self.assertEqual(spender_actual_balance, spender_new_decayed_balance)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,84 @@
# standard imports
import os
import unittest
import json
import logging
# external imports
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt
# local imports
from erc20_demurrage_token import DemurrageToken
# test imports
from tests.base import TestDemurrageReal
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageReal):
def test_simple_example(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 100000000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], 100000000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[3], 50000000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
self.backend.time_travel(self.start_time + self.period_seconds + 1)
(tx_hash, o) = c.change_period(self.address, self.accounts[1])
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])
redistribution = self.rpc.do(o)
logg.debug('redistribution {}'.format(redistribution))
o = c.to_redistribution_period(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
period = c.parse_to_redistribution_item(r)
logg.debug('period {}'.format(period))
o = c.to_redistribution_participants(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
participants = c.parse_to_redistribution_item(r)
logg.debug('participants {}'.format(participants))
o = c.to_redistribution_supply(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
supply = c.parse_to_redistribution_item(r)
logg.debug('supply {}'.format(supply))
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
modifier = c.parse_to_redistribution_item(r)
logg.debug('modifier {}'.format(modifier))
if __name__ == '__main__':
unittest.main()

View File

@ -246,7 +246,7 @@ contract DemurrageTokenMultiCap {
return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants; return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants;
} }
// Serializes the number of participants part of the redistribution word // Serializes the demurrage modifier part of the redistribution word
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) { function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage; return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
} }
@ -417,6 +417,8 @@ contract DemurrageTokenMultiCap {
uint256 periodTimestamp; uint256 periodTimestamp;
uint256 nextPeriod; uint256 nextPeriod;
applyDemurrage();
currentRedistribution = checkPeriod(); currentRedistribution = checkPeriod();
if (currentRedistribution == bytes32(0x00)) { if (currentRedistribution == bytes32(0x00)) {
return false; return false;
@ -426,7 +428,7 @@ contract DemurrageTokenMultiCap {
nextPeriod = currentPeriod + 1; nextPeriod = currentPeriod + 1;
periodTimestamp = getPeriodTimeDelta(currentPeriod); periodTimestamp = getPeriodTimeDelta(currentPeriod);
applyDemurrage(); //applyDemurrage();
currentDemurrageAmount = demurrageAmount; currentDemurrageAmount = demurrageAmount;
demurrageCounts = demurrageCycles(periodTimestamp); demurrageCounts = demurrageCycles(periodTimestamp);