diff --git a/synapse/models/crypto.py b/synapse/models/crypto.py index d582e0afd8..209eae7570 100644 --- a/synapse/models/crypto.py +++ b/synapse/models/crypto.py @@ -45,6 +45,9 @@ def getModelDefs(self): ('crypto:currency:transaction', ('guid', {}), { 'doc': 'An individual crypto currency transaction recorded on the blockchain.', }), + ('crypto:currency:bridge:swap', ('guid', {}), { + 'doc': 'A cross-chain swap which bridges value between two transactions on different chains.', + }), ('crypto:currency:block', ('comp', {'fields': ( ('coin', 'crypto:currency:coin'), ('offset', 'int'), @@ -86,6 +89,18 @@ def getModelDefs(self): 'doc': 'A smart contract effect which grants a non-owner address the ability to manipulate fungible tokens.', 'interfaces': ('crypto:smart:effect',), }), + ('crypto:smart:effect:freeze', ('guid', {}), { + 'doc': 'A smart contract effect which freezes or unfreezes an address on an issuer blocklist.', + 'interfaces': ('crypto:smart:effect',), + }), + ('crypto:smart:effect:seize', ('guid', {}), { + 'doc': 'A smart contract effect which destroys the tokens held by an address.', + 'interfaces': ('crypto:smart:effect',), + }), + ('crypto:smart:effect:swaptokens', ('guid', {}), { + 'doc': 'A smart contract effect which swaps one token or currency for another.', + 'interfaces': ('crypto:smart:effect',), + }), # TODO crypto:smart:effect:call - call another smart contract # TODO crypto:smart:effect:giveproxy - grant your proxy for a token based vote ('crypto:payment:input', ('guid', {}), { @@ -94,6 +109,9 @@ def getModelDefs(self): ('crypto:payment:output', ('guid', {}), { 'doc': 'A payment received from a transaction.', }), + ('crypto:payment:fee', ('guid', {}), { + 'doc': 'A fee paid to execute a transaction.', + }), ('crypto:smart:token', ('comp', {'fields': (('contract', 'crypto:smart:contract'), ('tokenid', 'hugenum'))}), { 'doc': 'A token managed by a smart contract.', }), @@ -222,6 +240,14 @@ def getModelDefs(self): ('value', ('econ:price', {}), { 'doc': 'The value of the currency received from the transaction.'}), )), + ('crypto:payment:fee', {}, ( + ('transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction the fee was paid for.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which paid the fee.'}), + ('value', ('econ:price', {}), { + 'doc': 'The value of the fee paid to execute the transaction.'}), + )), ('crypto:currency:transaction', {}, ( ('hash', ('hex', {}), { 'doc': 'The unique transaction hash for the transaction.'}), @@ -274,6 +300,29 @@ def getModelDefs(self): # TODO break out args/retvals and maybe make humon repr? )), + ('crypto:currency:bridge:swap', {}, ( + ('name', ('str', {'strip': True}), { + 'doc': 'The name of the bridge protocol which executed the swap.'}), + ('matched', ('bool', {}), { + 'doc': 'Set to true if the two transactions were matched together by an external observer.'}), + ('source:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which sent the bridged value.'}), + ('source:address', ('crypto:currency:address', {}), { + 'doc': 'The address which sent the bridged value.'}), + ('source:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the sent tokens. This is not set if the sent value was a native currency.'}), + ('source:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency sent.'}), + ('target:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which received the bridged value.'}), + ('target:address', ('crypto:currency:address', {}), { + 'doc': 'The address which received the bridged value.'}), + ('target:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the received tokens. This is not set if the received value was a native currency.'}), + ('target:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency received.'}), + )), + ('crypto:currency:block', {}, ( ('coin', ('crypto:currency:coin', {}), { 'doc': 'The coin/blockchain this block resides on.', 'ro': True, }), @@ -398,6 +447,39 @@ def getModelDefs(self): 'doc': 'The hex encoded amount of tokens the proxy is allowed to manipulate.'}), )), + ('crypto:smart:effect:freeze', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which enforces the freeze.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which was frozen or unfrozen.'}), + ('frozen', ('bool', {}), { + 'doc': 'Set to true if the address was frozen or false if it was unfrozen.'}), + )), + + ('crypto:smart:effect:seize', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the tokens.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address whose tokens were destroyed.'}), + ('amount', ('hugenum', {}), { + 'doc': 'The amount of tokens destroyed.'}), + )), + + ('crypto:smart:effect:swaptokens', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The pool or router contract which executed the swap.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which swapped the tokens.'}), + ('sent:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the sent tokens. This is not set if the sent value was a native currency.'}), + ('sent:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency sent by the address.'}), + ('received:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the received tokens. This is not set if the received value was a native currency.'}), + ('received:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency received by the address.'}), + )), + ('crypto:currency:address', {}, ( ('coin', ('crypto:currency:coin', {}), { 'doc': 'The crypto coin to which the address belongs.', 'ro': True, }), diff --git a/synapse/tests/test_model_crypto.py b/synapse/tests/test_model_crypto.py index c93350f932..25b37a30ee 100644 --- a/synapse/tests/test_model_crypto.py +++ b/synapse/tests/test_model_crypto.py @@ -121,6 +121,13 @@ async def test_model_crypto_currency(self): self.eq(payees[0].get('index'), 0) self.eq(payees[0].get('address'), ('btc', '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')) + payfees = await core.nodes('[ crypto:payment:fee=(t1, feepayer) :transaction=(t1,) :address=(btc, 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2) :value=0.0001 ]') + self.len(1, payfees) + self.eq(payfees[0].get('value'), '0.0001') + self.eq(payfees[0].get('address'), ('btc', '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')) + self.len(1, await core.nodes('crypto:payment:fee -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:payment:fee -> crypto:currency:address')) + payor = payors[0].ndef[1] payee = payees[0].ndef[1] @@ -355,6 +362,119 @@ async def test_model_crypto_currency(self): self.len(1, await core.nodes('crypto:smart:effect:proxytokens -> crypto:currency:transaction')) self.len(1, await core.nodes('crypto:smart:effect:proxytokens -> crypto:smart:contract')) + nodes = await core.nodes(''' + [ + crypto:smart:effect:freeze=* + :index=0 + :transaction=* + :contract=* + :address=(eth, 0xd73d44149deb8aabd4b53197654a286dedfda827) + :frozen=$lib.true + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 0) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0xd73d44149deb8aabd4b53197654a286dedfda827')) + self.true(node.get('frozen')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:smart:contract')) + + nodes = await core.nodes(''' + [ + crypto:smart:effect:seize=* + :index=0 + :transaction=* + :contract=* + :address=(eth, 0xd73d44149deb8aabd4b53197654a286dedfda827) + :amount=960007.90 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 0) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0xd73d44149deb8aabd4b53197654a286dedfda827')) + self.eq(node.get('amount'), '960007.9') + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:smart:contract')) + + nodes = await core.nodes(''' + [ + crypto:smart:effect:swaptokens=* + :index=179 + :transaction=* + :contract=* + :address=(eth, 0x241ebd14e40899ddb1191c140f85c37917b0505b) + :sent:contract=* + :sent:amount=0.025194371619325567 + :received:contract=* + :received:amount=22574.721 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 179) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0x241ebd14e40899ddb1191c140f85c37917b0505b')) + self.nn(node.get('sent:contract')) + self.eq(node.get('sent:amount'), '0.025194371619325567') + self.nn(node.get('received:contract')) + self.eq(node.get('received:amount'), '22574.721') + self.len(1, await core.nodes('crypto:smart:effect:swaptokens -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:swaptokens -> crypto:currency:transaction')) + self.len(3, await core.nodes('crypto:smart:effect:swaptokens -> crypto:smart:contract')) + + nodes = await core.nodes(''' + [ + crypto:currency:bridge:swap=(bridge1,) + :name="deBridge.finance" + :matched=true + :source:transaction=(otxn,) + :source:address=(eth, 0xaaaa) + :source:contract=* + :source:amount=1.5 + :target:transaction=(dtxn,) + :target:address=(bsc, 0xbbbb) + :target:contract=* + :target:amount=1490.0 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('name'), 'deBridge.finance') + self.eq(node.get('matched'), True) + self.eq(node.get('source:address'), ('eth', '0xaaaa')) + self.nn(node.get('source:contract')) + self.eq(node.get('source:amount'), '1.5') + self.eq(node.get('target:address'), ('bsc', '0xbbbb')) + self.nn(node.get('target:contract')) + self.eq(node.get('target:amount'), '1490') + + # the swap is reachable from either leg via its indexed transaction prop, and bridges to the other leg + self.len(1, await core.nodes('crypto:currency:bridge:swap:source:transaction=(otxn,)')) + self.len(1, await core.nodes('crypto:currency:bridge:swap:target:transaction=(dtxn,)')) + self.len(2, await core.nodes('crypto:currency:bridge:swap=(bridge1,) -> crypto:currency:transaction')) + nodes = await core.nodes('crypto:currency:bridge:swap:source:transaction=(otxn,) :target:transaction -> crypto:currency:transaction') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('crypto:currency:transaction', s_common.guid(('dtxn',)))) + + # is:matched does not imply the target leg is populated + nodes = await core.nodes(''' + [ + crypto:currency:bridge:swap=(bridge2,) + :matched=true + :source:transaction=(otxn2,) + :source:address=(eth, 0xcccc) + :source:amount=2.0 + :target:transaction=(dtxn2,) + ]''') + self.len(1, nodes) + node = nodes[0] + self.nn(node.get('target:transaction')) + self.none(node.get('target:address')) + self.none(node.get('target:contract')) + self.none(node.get('target:amount')) + nodes = await core.nodes(''' [ crypto:smart:token=(2bdea834252a220b61aadf592cc0de66, 30)