Skip to content

Commit

Permalink
system contract improvements (#364)
Browse files Browse the repository at this point in the history
* added methods to members to create payment

* rm balance_stream from vote_types

* update test

* updated currency contract

* restore seed stream to make tests pass.

* restore original dao funding rate

* currency update to latest
  • Loading branch information
duelingbenjos authored Jan 30, 2025
1 parent 4292622 commit 1cf643a
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 32 deletions.
29 changes: 18 additions & 11 deletions src/xian/tools/genesis/contracts/currency.s.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def seed(vk: str):
# 1824 * 24 * 60 * 60 = 157593600 (seconds in duration)
# 16666666.65 / 157593600 (release per second)

# Leaving this in for reference, even though stream was remove for rcnet
setup_seed_stream(stream_id="team_vesting", sender="team_vesting", receiver="team_lock", rate=0.10575725568804825, duration_days=1824)

# DAO FUNDING STREAM
Expand Down Expand Up @@ -289,25 +290,31 @@ def balance_stream(stream_id: str):
def change_close_time(stream_id: str, new_close_time: str):
new_close_time = strptime_ymdhms(new_close_time)

assert streams[stream_id, STATUS_KEY], 'Stream does not exist.'
assert streams[stream_id, STATUS_KEY] == STREAM_ACTIVE, 'Stream is not active.'
assert streams[stream_id, STATUS_KEY], "Stream does not exist."
assert streams[stream_id, STATUS_KEY] == STREAM_ACTIVE, "Stream is not active."

sender = streams[stream_id, SENDER_KEY]
receiver = streams[stream_id, RECEIVER_KEY]
begins = streams[stream_id, BEGIN_KEY]

assert ctx.caller == sender, "Only sender can change the close time of a stream."

assert ctx.caller == sender, 'Only sender can change the close time of a stream.'

if new_close_time < streams[stream_id, BEGIN_KEY] and now < streams[stream_id, BEGIN_KEY]:
streams[stream_id, CLOSE_KEY] = streams[stream_id, BEGIN_KEY]
elif new_close_time <= now:
# If new close time is in the past or before begin time, close immediately or at begin time
if new_close_time <= now:
streams[stream_id, CLOSE_KEY] = now
elif new_close_time < begins:
streams[stream_id, CLOSE_KEY] = begins
else:
streams[stream_id, CLOSE_KEY] = new_close_time

StreamCloseChangeEvent({"receiver":receiver, "sender":sender, "stream_id":stream_id, "time":str(new_close_time)})


StreamCloseChangeEvent(
{
"receiver": receiver,
"sender": sender,
"stream_id": stream_id,
"time": str(streams[stream_id, CLOSE_KEY]),
}
)

# Set the stream inactive.
# A stream must be balanced before it can be finalized.
Expand All @@ -329,7 +336,7 @@ def finalize_stream(stream_id: str):
rate = streams[stream_id, RATE_KEY]
claimed = streams[stream_id, CLAIMED_KEY]

assert now <= closes, 'Stream has not closed yet.'
assert closes <= now, 'Stream has not closed yet.'

outstanding_balance = calc_outstanding_balance(begins, closes, rate, claimed)

Expand Down
38 changes: 35 additions & 3 deletions src/xian/tools/genesis/contracts/dao.s.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,44 @@

@export
def transfer_from_dao(args: dict):
contract_name = args.get('contract_name')
amount = args.get('amount')
to = args.get('to')

assert contract_name is not None, 'Contract name is required'
assert amount > 0, 'Amount must be greater than 0'
currency.transfer(amount=amount, to=to)
assert to is not None, 'To is required'

contract = importlib.import_module(contract_name)
contract.transfer(amount=amount, to=to)

@export
def balance_stream(stream_id: str):
currency.balance_stream(stream_id=stream_id)

@export
def create_stream(args: dict):
receiver = args.get('receiver')
rate = args.get('rate')
begins = args.get('begins')
closes = args.get('closes')
currency.create_stream(receiver=receiver, rate=rate, begins=begins, closes=closes)

@export
def balance_dao_stream():
currency.balance_stream(stream_id="dao_funding_stream")
def change_close_time(args: dict):
stream_id = args.get('stream_id')
new_close_time = args.get('new_close_time')
assert stream_id is not None, 'Stream ID is required'
assert new_close_time is not None, 'New close time is required'
currency.change_close_time(stream_id=stream_id, new_close_time=new_close_time)

@export
def finalize_stream(args: dict):
stream_id = args.get('stream_id')
currency.finalize_stream(stream_id=stream_id)

@export
def close_balance_finalize(args: dict):
stream_id = args.get('stream_id')
assert stream_id is not None, 'Stream ID is required'
currency.close_balance_finalize(stream_id=stream_id)
32 changes: 24 additions & 8 deletions src/xian/tools/genesis/contracts/members.s.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@
@construct
def seed(genesis_nodes: list, genesis_registration_fee: int):
nodes.set(genesis_nodes)
types.set(["add_member", "remove_member", "change_registration_fee", "reward_change", "dao_payout", "stamp_cost_change", "change_types"])
types.set([
"add_member",
"remove_member",
"change_registration_fee",
"reward_change",
"dao_payout",
"stamp_cost_change",
"change_types",
"create_stream",
"change_close_time",
"finalize_stream",
"close_balance_finalize",
"topic_vote"
])
total_votes.set(0)
registration_fee.set(genesis_registration_fee)

Expand Down Expand Up @@ -98,15 +111,22 @@ def finalize_vote(proposal_id: int):
registration_fee.set(cur_vote["arg"])
elif cur_vote["type"] == "change_types":
types.set(cur_vote["arg"])
elif cur_vote["type"] == "create_stream":
dao.create_stream(args=cur_vote["arg"])
elif cur_vote["type"] == "change_close_time":
dao.change_close_time(args=cur_vote["arg"])
elif cur_vote["type"] == "finalize_stream":
dao.finalize_stream(args=cur_vote["arg"])
elif cur_vote["type"] == "close_balance_finalize":
dao.close_balance_finalize(args=cur_vote["arg"])

cur_vote["finalized"] = True
votes[proposal_id] = cur_vote
return cur_vote


@export
def balance_dao_stream():
dao.balance_dao_stream()
def balance_stream(stream_id: str):
return dao.balance_stream(stream_id=stream_id)

def force_leave(node: str):
pending_leave[node] = now + datetime.timedelta(days=7)
Expand Down Expand Up @@ -141,7 +161,3 @@ def unregister():
currency.transfer(holdings[ctx.caller], ctx.caller)
pending_registrations[ctx.caller] = False
holdings[ctx.caller] = 0

@export
def balance_stream():
dao.balance_dao_stream()
21 changes: 13 additions & 8 deletions tests/governance/test_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ def vote_dao_payout(self):
"function": "propose_vote",
"kwargs": {
"type_of_vote": "dao_payout",
"arg": {"amount": 100000, "to": "new_node"},
"arg": {"amount": 100000, "to": "new_node", "contract_name": "currency"},
},
"stamps_supplied": 1000,
},
Expand Down Expand Up @@ -943,13 +943,18 @@ def test_types_change(self):
self.assertEqual(
self.masternodes.types.get(),
[
"add_member",
"remove_member",
"change_registration_fee",
"reward_change",
"dao_payout",
"stamp_cost_change",
"change_types",
"add_member",
"remove_member",
"change_registration_fee",
"reward_change",
"dao_payout",
"stamp_cost_change",
"change_types",
"create_stream",
"change_close_time",
"finalize_stream",
"close_balance_finalize",
"topic_vote"
],
)
self.vote_types_change()
Expand Down
110 changes: 110 additions & 0 deletions tests/system/test_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,5 +802,115 @@ def test_approve_overwrites_previous_allowance(self):
# THEN the new allowance should overwrite the old one
self.assertEqual(new_allowance, 200)

def test_finalize_stream_before_close_time(self):
# GIVEN a stream setup
sender = 'mary'
receiver = 'janine'
begins = Datetime(year=2023, month=1, day=1, hour=0, minute=0)
closes = Datetime(year=2024, month=1, day=1, hour=0, minute=0)
current_time = Datetime(year=2023, month=6, day=1, hour=0, minute=0) # Middle of the stream period
seconds_in_period = (closes - begins).seconds
self.currency.balances[sender] = seconds_in_period
self.currency.balances[receiver] = 0
rate = 1

stream_id = self.currency.create_stream(receiver=receiver, rate=rate, begins=str(begins), closes=str(closes), signer=sender)

# Balance the stream up to current time
self.currency.balance_stream(stream_id=stream_id, signer=receiver, environment={"now": current_time})

# WHEN attempting to finalize before close time
# THEN it should fail
with self.assertRaises(AssertionError) as context:
self.currency.finalize_stream(stream_id=stream_id, signer=receiver, environment={"now": current_time})
self.assertIn("Stream has not closed yet", str(context.exception))

def test_finalize_stream_exactly_at_close_time(self):
# GIVEN a stream setup
sender = 'mary'
receiver = 'janine'
begins = Datetime(year=2023, month=1, day=1, hour=0, minute=0)
closes = Datetime(year=2024, month=1, day=1, hour=0, minute=0)
seconds_in_period = (closes - begins).seconds
self.currency.balances[sender] = seconds_in_period
self.currency.balances[receiver] = 0
rate = 1

stream_id = self.currency.create_stream(receiver=receiver, rate=rate, begins=str(begins), closes=str(closes), signer=sender)

# Balance the stream
self.currency.balance_stream(stream_id=stream_id, signer=receiver, environment={"now": closes})

# WHEN finalizing exactly at close time
finalize_res = self.currency.finalize_stream(
stream_id=stream_id,
signer=receiver,
environment={"now": closes},
return_full_output=True
)

# THEN it should succeed
expected_events = [{
'contract': 'currency',
'event': 'StreamFinalized',
'signer': 'janine',
'caller': 'janine',
'data_indexed': {
'receiver': 'janine',
'sender': 'mary',
'stream_id': stream_id
},
'data': {
'time': '2024-01-01 00:00:00'
}
}]
self.assertEqual(finalize_res['events'], expected_events)
self.assertEqual(self.currency.streams[stream_id, 'status'], 'finalized')
self.assertEqual(self.currency.streams[stream_id, 'claimed'], seconds_in_period)

def test_finalize_stream_after_close_time(self):
# GIVEN a stream setup
sender = 'mary'
receiver = 'janine'
begins = Datetime(year=2023, month=1, day=1, hour=0, minute=0)
closes = Datetime(year=2024, month=1, day=1, hour=0, minute=0)
current_time = Datetime(year=2024, month=2, day=1, hour=0, minute=0) # After close time
seconds_in_period = (closes - begins).seconds
self.currency.balances[sender] = seconds_in_period
self.currency.balances[receiver] = 0
rate = 1

stream_id = self.currency.create_stream(receiver=receiver, rate=rate, begins=str(begins), closes=str(closes), signer=sender)

# Balance the stream
self.currency.balance_stream(stream_id=stream_id, signer=receiver, environment={"now": closes})

# WHEN finalizing after close time
finalize_res = self.currency.finalize_stream(
stream_id=stream_id,
signer=receiver,
environment={"now": current_time},
return_full_output=True
)

# THEN it should succeed
expected_events = [{
'contract': 'currency',
'event': 'StreamFinalized',
'signer': 'janine',
'caller': 'janine',
'data_indexed': {
'receiver': 'janine',
'sender': 'mary',
'stream_id': stream_id
},
'data': {
'time': '2024-02-01 00:00:00'
}
}]
self.assertEqual(finalize_res['events'], expected_events)
self.assertEqual(self.currency.streams[stream_id, 'status'], 'finalized')
self.assertEqual(self.currency.streams[stream_id, 'claimed'], seconds_in_period)

if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 1cf643a

Please sign in to comment.