Introduce sim example
This commit is contained in:
parent
2f5bb63f9a
commit
c69d115965
File diff suppressed because one or more lines are too long
@ -97,6 +97,7 @@ class DemurrageTokenSimulation:
|
||||
self.period = 1
|
||||
self.period_txs = []
|
||||
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(
|
||||
self.last_block,
|
||||
@ -126,6 +127,32 @@ class DemurrageTokenSimulation:
|
||||
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):
|
||||
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)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value)
|
||||
self.rpc.do(o)
|
||||
self.next_block()
|
||||
self.__next_block()
|
||||
self.__check_tx(tx_hash)
|
||||
self.period_txs.append(tx_hash)
|
||||
logg.info('mint {} tokens to {} - {}'.format(value, recipient, 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)
|
||||
(tx_hash, o) = c.transfer(self.address, sender, recipient, value)
|
||||
self.rpc.do(o)
|
||||
self.next_block()
|
||||
self.__next_block()
|
||||
self.__check_tx(tx_hash)
|
||||
self.period_txs.append(tx_hash)
|
||||
logg.info('transfer {} tokens from {} to {} - {}'.format(value, sender, recipient, tx_hash))
|
||||
return tx_hash
|
||||
|
||||
|
||||
@ -163,42 +192,51 @@ class DemurrageTokenSimulation:
|
||||
return self.caller_contract.parse_balance_of(r)
|
||||
|
||||
|
||||
def next_block(self):
|
||||
def __next_block(self):
|
||||
hsh = self.eth_helper.mine_block()
|
||||
o = block_by_hash(hsh)
|
||||
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):
|
||||
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))
|
||||
self.last_timestamp = target_timestamp
|
||||
|
||||
self.eth_helper.time_travel(self.last_timestamp)
|
||||
self.next_block()
|
||||
self.__next_block()
|
||||
|
||||
o = block_by_number(self.last_block)
|
||||
r = self.rpc.do(o)
|
||||
self.last_block = r['number']
|
||||
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)
|
||||
(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.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)
|
||||
self.next_block()
|
||||
|
||||
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.__next_block()
|
||||
|
||||
o = block_latest()
|
||||
self.last_block = self.rpc.do(o)
|
||||
@ -212,7 +250,7 @@ class DemurrageTokenSimulation:
|
||||
raise RuntimeError('demurrage step failed on block {}'.format(self.last_block))
|
||||
|
||||
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_txs = []
|
||||
|
||||
|
@ -223,6 +223,51 @@ class DemurrageToken(ERC20):
|
||||
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):
|
||||
o = jsonrpc_template()
|
||||
o['method'] = 'eth_call'
|
||||
@ -323,6 +368,11 @@ class DemurrageToken(ERC20):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_to_redistribution_item(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_supply_cap(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
76
python/examples/sim.py
Normal file
76
python/examples/sim.py
Normal 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))
|
@ -163,3 +163,51 @@ class TestDemurrageCap(TestDemurrage):
|
||||
self.deploy(c, 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))
|
||||
|
||||
|
@ -60,7 +60,7 @@ class TestSim(unittest.TestCase):
|
||||
self.sim.next()
|
||||
|
||||
balance = self.sim.balance(self.sim.actors[0])
|
||||
self.assertEqual(balance, 89995500)
|
||||
self.assertEqual(balance, 90005520)
|
||||
|
||||
balance = self.sim.balance(self.sim.actors[1])
|
||||
self.assertEqual(balance, 99995000)
|
||||
|
@ -26,7 +26,6 @@ logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestRedistribution(TestDemurrageDefault):
|
||||
|
||||
def test_debug_periods(self):
|
||||
@ -261,3 +260,5 @@ class TestRedistribution(TestDemurrageDefault):
|
||||
self.assertEqual(spender_actual_balance, spender_new_decayed_balance)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
84
python/tests/test_redistribution_real.py
Normal file
84
python/tests/test_redistribution_real.py
Normal 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()
|
@ -246,7 +246,7 @@ contract DemurrageTokenMultiCap {
|
||||
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) {
|
||||
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
|
||||
}
|
||||
@ -417,6 +417,8 @@ contract DemurrageTokenMultiCap {
|
||||
uint256 periodTimestamp;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
|
||||
currentRedistribution = checkPeriod();
|
||||
if (currentRedistribution == bytes32(0x00)) {
|
||||
return false;
|
||||
@ -426,7 +428,7 @@ contract DemurrageTokenMultiCap {
|
||||
nextPeriod = currentPeriod + 1;
|
||||
periodTimestamp = getPeriodTimeDelta(currentPeriod);
|
||||
|
||||
applyDemurrage();
|
||||
//applyDemurrage();
|
||||
currentDemurrageAmount = demurrageAmount;
|
||||
|
||||
demurrageCounts = demurrageCycles(periodTimestamp);
|
||||
|
Reference in New Issue
Block a user