From a13d4d17f401beb338352ff9ca7712ee37839d7d Mon Sep 17 00:00:00 2001 From: luc10921 Date: Fri, 26 May 2023 17:36:23 -0300 Subject: [PATCH] CU-864ej3bwe - Change Neo3Boa Documentation describing the features (first iteration) --- README.md | 876 +--------- boa3/builtin/compile_time/__init__.py | 90 + boa3/builtin/contract/__init__.py | 47 + boa3/builtin/interop/blockchain/__init__.py | 125 ++ boa3/builtin/interop/contract/__init__.py | 58 +- boa3/builtin/interop/crypto/__init__.py | 40 + boa3/builtin/interop/iterator/__init__.py | 5 + boa3/builtin/interop/json/__init__.py | 6 + boa3/builtin/interop/policy/__init__.py | 12 + boa3/builtin/interop/role/__init__.py | 3 + boa3/builtin/interop/runtime/__init__.py | 111 ++ boa3/builtin/interop/stdlib/__init__.py | 81 + boa3/builtin/interop/storage/__init__.py | 32 + boa3/builtin/math.py | 15 + .../nativecontract/contractmanagement.py | 58 + boa3/builtin/nativecontract/cryptolib.py | 19 + boa3/builtin/nativecontract/gas.py | 33 + boa3/builtin/nativecontract/ledger.py | 102 ++ boa3/builtin/nativecontract/neo.py | 79 +- boa3/builtin/nativecontract/oracle.py | 7 + boa3/builtin/nativecontract/policy.py | 12 + boa3/builtin/nativecontract/rolemanagement.py | 3 + boa3/builtin/nativecontract/stdlib.py | 87 + boa3/builtin/type/__init__.py | 9 + .../boa3-builtin-interop-blockchain.rst | 10 - .../builtin/interop/boa3-builtin-interop.rst | 3 + ...oa3-builtin-interop-contract-callflags.rst | 4 - ...boa3-builtin-interop-contract-contract.rst | 4 - ...ltin-interop-contract-contractmanifest.rst | 4 - .../boa3-builtin-interop-contract.rst | 9 - .../oracle/boa3-builtin-interop-oracle.rst | 4 + .../policy/boa3-builtin-interop-policy.rst | 4 + .../role/boa3-builtin-interop-role.rst | 4 + .../runtime/boa3-builtin-interop-runtime.rst | 10 - .../storage/boa3-builtin-interop-storage.rst | 10 - docs/source/calling-smart-contracts.md | 125 ++ docs/source/code-reference.rst | 311 ---- docs/source/conceptual-overview.rst | 52 - docs/source/conf.py | 6 +- docs/source/getting-started.md | 110 ++ docs/source/getting-started.rst | 162 -- docs/source/index.rst | 5 +- docs/source/package-reference.rst | 4 +- docs/source/testing-and-debugging.md | 99 ++ docs/source/tutorials.rst | 1465 ----------------- requirements_dev.txt | 5 +- 46 files changed, 1396 insertions(+), 2924 deletions(-) delete mode 100644 docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-callflags.rst delete mode 100644 docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contract.rst delete mode 100644 docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contractmanifest.rst create mode 100644 docs/source/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.rst create mode 100644 docs/source/boa3/builtin/interop/policy/boa3-builtin-interop-policy.rst create mode 100644 docs/source/boa3/builtin/interop/role/boa3-builtin-interop-role.rst create mode 100644 docs/source/calling-smart-contracts.md delete mode 100644 docs/source/code-reference.rst delete mode 100644 docs/source/conceptual-overview.rst create mode 100644 docs/source/getting-started.md delete mode 100644 docs/source/getting-started.rst create mode 100644 docs/source/testing-and-debugging.md delete mode 100644 docs/source/tutorials.rst diff --git a/README.md b/README.md index 83159329b..42f1ce27f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@
Made by COZ.IO

-

Neo3-boa · neo-mamba

+

+ neo3-boa · neo-mamba · cpm

@@ -34,16 +35,8 @@ - [Installation](#installation) - [Pip (Recommended)](#pip-recommended) - [Build from Source (Optional)](#build-from-source-optional) - - [Compiling your Smart Contract](#compiling-your-smart-contract) - - [Using CLI](#using-cli) - - [Using Python Script](#using-python-script) - - [Configuring the Debugger](#configuring-the-debugger) - - [Neo Test Runner](#neo-test-runner) - - [Downloading](#downloading) - - [Testing](#testing) - [Docs](#docs) - [Reference Examples](#reference-examples) -- [Python Supported Features](#python-supported-features) - [Neo Python Suite Projects](#neo-python-suite-projects) - [Contributing](#contributing) - [License](#license) @@ -96,126 +89,9 @@ $ pip install wheel $ pip install -e . ``` -### Compiling your Smart Contract - -#### Using CLI - -```shell -$ neo3-boa path/to/your/file.py -``` - -
- -> Note: When resolving compilation errors it is recommended to resolve the first reported error and try to compile again. An error can have a cascading effect and throw more errors all caused by the first. - -#### Using Python Script - -```python -from boa3.boa3 import Boa3 - -Boa3.compile_and_save('path/to/your/file.py') -``` - -### Configuring the Debugger -Neo3-boa is compatible with the [Neo Debugger](https://github.com/neo-project/neo-debugger). -Debugger launch configuration example: -``` -{ - //Launch configuration example for Neo3-boa. - //Make sure you compile your smart-contract before you try to debug it. - "version": "0.2.0", - "configurations": [ - { - "name": "example.nef", - "type": "neo-contract", - "request": "launch", - "program": "${workspaceFolder}\\example.nef", - "operation": "main", - "args": [], - "storage": [], - "runtime": { - "witnesses": { - "check-result": true - } - } - } - ] -} -``` - -It's necessary to generate the nef debugger info file to use Neo Debugger. - -#### Using CLI - -```shell -$ neo3-boa path/to/your/file.py -d|--debug -``` - -#### Using Python Script - -```python -from boa3.boa3 import Boa3 - -Boa3.compile_and_save('path/to/your/file.py', debug=True) -``` - - -### Neo Test Runner - -#### Downloading - -Install [Neo-Express](https://github.com/neo-project/neo-express#neo-express-and-neo-trace) and [Neo Test Runner](https://github.com/ngdenterprise/neo-test#neo-test-runner). - -```shell -$ dotnet tool install Neo.Express -$ dotnet tool install Neo.Test.Runner -``` - -#### Testing - -Create a Python Script, import the NeoTestRunner class, and define a function to test your smart contract. In this -function you'll need to call the method `call_contract()`. Its parameters are the path of the compiled smart contract, -the smart contract's method, and the arguments if necessary. Then assert the result of your invoke to see if it's correct. - -Your Python Script should look something like this: - -```python -from boa3_test.test_drive.testrunner.neo_test_runner import NeoTestRunner - - -def test_hello_world_main(): - neoxp_folder = '{path-to-neo-express-directory}' - project_root_folder = '{path-to-project-root-folder}' - path = f'{project_root_folder}/boa3_test/examples/hello_world.nef' - runner = NeoTestRunner(neoxp_folder) - - invoke = runner.call_contract(path, 'Main') - runner.execute() - assert invoke.result is None -``` - -Alternatively you can change the value of `boa3.env.NEO_EXPRESS_INSTANCE_DIRECTORY` to the path of your .neo-express -data file: - -```python -from boa3_test.test_drive.testrunner.neo_test_runner import NeoTestRunner -from boa3.internal import env - -env.NEO_EXPRESS_INSTANCE_DIRECTORY = '{path-to-neo-express-directory}' - - -def test_hello_world_main(): - root_folder = '{path-to-project-root-folder}' - path = f'{root_folder}/boa3_test/examples/hello_world.nef' - runner = NeoTestRunner() # the default path to the Neo-Express is the one on env.NEO_EXPRESS_INSTANCE_DIRECTORY - - invoke = runner.call_contract(path, 'Main') - runner.execute() - assert invoke.result is None -``` - ## Docs -You can [read the docs here](https://docs.coz.io/neo3/boa/index.html). Please check our examples for reference. +Check out our [getting started documentation](https://dojo.coz.io/neo3/boa/getting-started.html) if you want to know +more about the usage of our compiler. Also check our examples below for reference. ## Reference Examples @@ -223,750 +99,6 @@ For an extensive collection of examples: - [Smart contract examples](/boa3_test/examples) - [Features tests](/boa3_test/test_sc) -## Python Supported Features - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StatusReleaseConvertsExample CodeContract Example Test
v0.3Local variable declarations and assignments -
-        
-  def func():
-    foo: int = 42
-    bar = foo
-        
-      
-
- List of examples -
v0.3Global variable declarations and assignments -
-        
-  foo: int = 42
-  bar = foo
-        
-      
-
- List of examples -
v0.4Global keyword -
-        
-  foo: int = 42
-  bar = foo
-        
-        
-  def func():
-    global foo
-    foo = 1
-        
-      
-
- List of examples -
v0.3Arithmetic operations -
-        
-  +, -, *, //, %
-        
-      
-
- List of examples -
v0.8Arithmetic operations -
-        
-  **
-        
-      
-
- List of examples -
🔜backlogArithmetic operations -
-        
-  /
-        
-      
-
-
v0.3Arithmetic augmented assignment operators -
-        
-  +=, -=, *=, //=, %=
-        
-      
-
- List of examples -
v0.8Arithmetic augmented assignment operators -
-        
-  **=
-        
-      
-
- List of examples -
🔜backlogArithmetic augmented assignment operators -
-        
-  /=
-        
-      
-
-
v0.3Relational operations -
-        
-  ==, !=, <, <=, >, >=, 
-  is None, is not None
-        
-      
-
- List of examples -
v0.8.3Relational operations -
-        
-  is, is not
-        
-      
-
- List of examples -
v0.3Bitwise operations -
-        
-  &, |, ~, ^, <<, >>
-        
-      
-
- List of examples -
v0.8.3Bitwise augmented assignment operators -
-        
-  &=, |=, ~=, ^=, <<=, >>=
-        
-      
-
- List of examples -
v0.3Boolean logic operations -
-        
-  and, or, not
-        
-      
-
- List of examples -
v0.3Tuple type -
-        
-  a = ('1', '2', '3')
-        
-      
-
- List of examples -
v0.3List type -
-        
-  a = ['1', '2', '3']
-        
-      
-
- List of examples -
v0.4List type -
-        
-  a.pop()
-        
-      
-
- List of examples -
v0.7List type -
-        
-  a.remove(1)
-  a.insert('example', 2)
-        
-      
-
- List of examples -
v0.3Dict type -
-        
-  a = {1:'1', 2:'2', 3:'3'}
-        
-      
-
- List of examples -
🔜backlogSet type -
-        
-  a = {'1', '2', '3'}
-        
-      
-
-
v0.3Bytes type -
-        
-  a = b'\x01\x02\x03\x04'
-        
-      
-
- List of examples -
v0.3Bytearray type -
-        
-  a = bytearray(b'\x01\x02\x03\x04')
-        
-      
-
- List of examples -
v0.8.2Optional type -
-        
-  a: Optional[int] = 5
-  a = 142
-  a = None
-        
-      
-
- List of examples -
v0.6.1Union type -
-        
-  a: Union[int, str] = 5
-  a = 142
-  a = 'example'
-        
-      
-
- List of examples -
v0.3While statement -
-        
-  foo = 0
-  while condition:
-    foo = foo + 2
-        
-      
-
- List of examples -
v0.3If, elif, else statements -
-        
-  if condition1:
-    foo = 0
-  elif condition2:
-    foo = 1
-  else:
-    bar = 2
-        
-      
-
- List of examples -
v0.3For statement -
-        
-  for x in (1, 2, 3):
-    ...
-        
-      
-
- List of examples -
v0.3Function call -
-        
-  def Main(num: int):
-    a = foo(num)
-    ...
-        
-        
-  def foo(num: int) -> int:
-    ...
-        
-      
-
- List of examples -
v0.3Built in function -
-        
-  a = len('hello')
-        
-      
-
- List of examples -
v0.4Built in function -
-        
-  a = range(1, 5, 2)
-  b = isinstance(5, str)
-  print(42)
-        
-      
-
- List of examples -
v0.7Built in function -
-        
-  a = max(7, 12)
-  b = min(1, 6)
-        
-      
-
- List of examples -
v0.8Built in function -
-        
-  a = abs(-5)
-        
-      
-
- List of examples -
v0.8.1Built in function -
-        
-  a = sum(list_of_num, 0)
-        
-      
-
- List of examples -
v0.8.3Built in function -
-        
-  a = max(7, 0, 12, 8)
-  b = min(1, 6, 2)
-  c = reversed([1, 2, 3, 4])
-        
-      
-
- List of examples -
v0.11.0Built in function -
-        
-  a = pow(2, 2)
-        
-      
-
- List of examples -
v0.3Multiple expressions in the same line -
-        
-  i = i + h; a = 1; b = 3 + a; count = 0
-        
-      
-
- List of examples -
v0.4Chained assignment -
-        
-  x = y = foo()
-        
-      
-
-
v0.3Sequence slicing -
-        
-  x = 'example'[2:4]
-  x = [1, 2, 3][:2]
-  x = 'example'[4:]
-  x = (1, 2, 3)[:]
-  x = 'example'[-4:-2]
-  x = 'example'[:-4]
-        
-      
-
- List of examples -
v0.10.0Sequence slicing -
-        
-  x = 'example'[2:4:2]
-  x = 'example'[::2]
-        
-      
-
- List of examples -
v0.3Assert -
-        
-  assert x % 2 == 0
-  assert x % 3 != 2, 'error message'
-        
-      
-
- List of examples -
v0.4Try except -
-        
-  try:
-    a = foo(b)
-  except Exception as e:
-    a = foo(b)
-        
-      
-
- List of examples -
v0.5Try except with finally -
-        
-  try:
-    a = foo(b)
-  except Exception as e:
-    a = zubs(b)
-  finally:
-    b = zubs(a)
-        
-      
-
- List of examples -
v0.4Continue, break - -
v0.11.2Pass - - List of examples -
v0.3ImportSupport to boa3.builtin packages. - List of examples -
v0.8.3ImportSupport to user created modules. - List of examples -
v0.10.0Class -
-        
-  class Foo:
-    def __init__(self, bar: Any):
-      pass
-        
-      
-
- List of examples -
- - ## Neo Python Suite Projects - **[Neo3-boa](https://github.com/CityOfZion/neo3-boa)**: Python smart contracts' compiler. diff --git a/boa3/builtin/compile_time/__init__.py b/boa3/builtin/compile_time/__init__.py index 23061f8d9..d3302e8ea 100644 --- a/boa3/builtin/compile_time/__init__.py +++ b/boa3/builtin/compile_time/__init__.py @@ -16,6 +16,14 @@ def CreateNewEvent(arguments: List[Tuple[str, type]] = [], event_name: str = '') """ Creates a new event. + >>> new_event: Event = CreateNewEvent( + ... [ + ... ('name', str), + ... ('amount', int) + ... ], + ... 'New Event' + ... ) + :param arguments: the list of the events args' names and types :type arguments: List[Tuple[str, type]] :param event_name: custom name of the event. It's filled with the variable name if not specified @@ -30,6 +38,39 @@ def public(name: str = None, safe: bool = True, *args, **kwargs): """ This decorator identifies which methods should be included in the abi file. + >>> @public # this method will be added to the abi + ... def callable_function() -> bool: + ... return True + { + "name": "callable_function", + "offset": 0, + "parameters": [], + "safe": false, + "returntype": "Boolean" + } + + >>> @public(name='callableFunction') # the method will be added with the different name to the abi + ... def callable_function() -> bool: + ... return True + { + "name": "callableFunction", + "offset": 0, + "parameters": [], + "safe": false, + "returntype": "Boolean" + } + + >>> @public(safe=True) # the method will be added with the safe flag to the abi + ... def callable_function() -> bool: + ... return True + { + "name": "callable_function", + "offset": 0, + "parameters": [], + "safe": true, + "returntype": "Boolean" + } + :param name: Identifier for this method that'll be used on the abi. If not specified, it'll be the same identifier from Python method definition :type name: str @@ -45,6 +86,12 @@ def metadata(*args): """ This decorator identifies the function that returns the metadata object of the smart contract. This can be used to only one function. Using this decorator in multiple functions will raise a compiler error. + + >>> @metadata # this indicates that this function will have information about the smart contract + ... def neo_metadata() -> NeoMetadata: # needs to return a NeoMetadata + ... meta = NeoMetadata() + ... meta.name = 'NewContractName' + ... return meta """ pass @@ -53,6 +100,15 @@ def contract(script_hash: ByteString): """ This decorator identifies a class that should be interpreted as an interface to an existing contract. + >>> @contract('0xd2a4cff31913016155e38e474a2c06d08be276cf') + ... class GASInterface: + ... @staticmethod + ... def symbol() -> str: + ... pass + ... @public + ... def main() -> str: + ... return "Symbol is " + GASInterface.symbol() + :param script_hash: Script hash of the interfaced contract :type script_hash: str or bytes """ @@ -73,6 +129,16 @@ def display_name(name: str): This decorator identifies which methods from a contract interface should have a different identifier from the one interfacing it. It only works in contract interface classes. + >>> @contract('0xd2a4cff31913016155e38e474a2c06d08be276cf') + ... class GASInterface: + ... @staticmethod + ... @display_name('totalSupply') + ... def total_supply() -> int: # the smart contract will call "totalSupply", but when writing the script you can call this method whatever you want to + ... pass + ... @public + ... def main() -> int: + ... return GASInterface.total_supply() + :param name: Method identifier from the contract manifest. :type name: str """ @@ -83,6 +149,15 @@ class NeoMetadata: """ This class stores the smart contract manifest information. + >>> @metadata + ... def neo_metadata() -> NeoMetadata: + ... meta = NeoMetadata() + ... meta.name = 'NewContractName' + ... meta.add_permission(methods=['onNEP17Payment']) + ... meta.add_trusted_source("0x1234567890123456789012345678901234567890") + ... meta.date = "2023/05/30" # this property will end up inside the extra property + ... return meta + :ivar name: the smart contract name. Will be the name of the file by default; :vartype type name: str :ivar supported_standards: Neo standards supported by this smart contract. Empty by default; @@ -152,6 +227,12 @@ def add_trusted_source(self, hash_or_address: str): """ Adds a valid contract hash, valid group public key, or the '*' wildcard to trusts. + >>> self.add_trusted_source("0x1234567890123456789012345678901234abcdef") + + >>> self.add_trusted_source("035a928f201639204e06b4368b1a93365462a8ebbff0b8818151b74faab3a2b61a") + + >>> self.add_trusted_source("*") + :param hash_or_address: a contract hash, group public key or '*' :type hash_or_address: str """ @@ -179,6 +260,9 @@ def add_group(self, pub_key: str, signature: str): """ Adds a pair of public key and signature to the groups in the manifest. + >>> self.add_group("031f64da8a38e6c1e5423a72ddd6d4fc4a777abe537e5cb5aa0425685cda8e063b", + ... "fhsOJNF3N5Pm3oV1b7wYTx0QVelYNu7whwXMi8GsNGFKUnu3ZG8z7oWLfzzEz9pbnzwQe8WFCALEiZhLD1jG/w==") + :param pub_key: public key of the group :type pub_key: str :param signature: signature of the contract hash encoded in Base64 @@ -204,6 +288,12 @@ def add_permission(self, *, contract: str = IMPORT_WILDCARD, methods: Union[List """ Adds a valid contract and a valid methods to the permissions in the manifest. + >>> self.add_permission(methods=['onNEP17Payment']) + + >>> self.add_permission(contract='0x3846a4aa420d9831044396dd3a56011514cd10e3', methods=['get_object']) + + >>> self.add_permission(contract='0333b24ee50a488caa5deec7e021ff515f57b7993b93b45d7df901e23ee3004916') + :param contract: a contract hash, group public key or '*' :type contract: str :param methods: a list of methods or '*' diff --git a/boa3/builtin/contract/__init__.py b/boa3/builtin/contract/__init__.py index 215810b88..0e4d76f85 100644 --- a/boa3/builtin/contract/__init__.py +++ b/boa3/builtin/contract/__init__.py @@ -24,6 +24,18 @@ The NEP-5 transfer event that will be triggered whenever a token is transferred, minted or burned. It needs the addresses of the sender, receiver and the amount transferred. +>>> Nep5TransferEvent(b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', # when calling, it will return None, but the event will be triggered +... b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', 100) +{ + 'name': 'transfer', + 'script hash': b'\\xee\\xc3\\x12\\xfd\\x12\\x95\\x84\\44\\x7f\\xb8\\xed\\x41\\xdc\\x86\\x33\\x95\\x10\\x10\\x9f\\x85', + 'state': { + 'from': b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', + 'to': b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', + 'amount': 100 + } +} + :meta hide-value: """ @@ -40,6 +52,19 @@ The NEP-11 Transfer event that will be triggered whenever a token is transferred, minted or burned. It needs the addresses of the sender, receiver, amount transferred and the id of the token. +>>> Nep11TransferEvent(b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', # when calling, it will return None, but the event will be triggered +... b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', 1, '01') +{ + 'name': 'Transfer', + 'script hash': b'\\x13\\xb4\\x51\\xa2\\x1c\\x10\\x12\\xd6\\x13\\x12\\x19\\x0c\\x15\\x61\\x9b\\x1b\\xd1\\xa2\\xf4\\xb2', + 'state': { + 'from': b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', + 'to': b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', + 'amount': 1, + 'tokenId': '01' + } +} + :meta hide-value: """ @@ -56,6 +81,18 @@ The NEP-17 Transfer event that will be triggered whenever a token is transferred, minted or burned. It needs the addresses of the sender, receiver and the amount transferred. +>>> Nep17TransferEvent(b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', # when calling, it will return None, but the event will be triggered +... b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', 100) +{ + 'name': 'Transfer', + 'script hash': b'\\x17\\xe3\\xca\\x91\\xca\\xb7\\xaf\\xdd\\xe6\\xba\\x07\\xaa\\xba\\xa1\\x66\\xab\\xcf\\x00\\x04\\x50', + 'state': { + 'from': b'\\xd1\\x17\\x92\\x82\\x12\\xc6\\xbe\\xfa\\x05\\xa0\\x23\\x07\\xa1\\x12\\x55\\x41\\x06\\x55\\x10\\xe6', + 'to': b'\\x18\\xb7\\x30\\x14\\xdf\\xcb\\xee\\x01\\x30\\x00\\x13\\x9b\\x8d\\xa0\\x13\\xfb\\x96\\xac\\xd1\\xc0', + 'amount': 100 + } +} + :meta hide-value: """ @@ -63,6 +100,10 @@ def abort(): """ Aborts the execution of a smart contract. + + >>> abort() # abort doesn't return anything by itself, but the execution will stop and the VMState will be FAULT + VMState.FAULT + """ pass @@ -90,6 +131,12 @@ def to_script_hash(data_bytes: Any) -> bytes: """ Converts a data to a script hash. + >>> to_script_hash(ECPoint(bytes(range(33)))) + b'\\x12\\xc8z\\xfb3k\\x1e4>\\xb3\\x83\\tK\\xc7\\xdch\\xe5\\xee\\xc7\\x98' + + >>> to_script_hash(b'1234567891') + b'\\x4b\\x56\\x34\\x17\\xed\\x99\\x7f\\x13\\x22\\x67\\x40\\x79\\x36\\x8b\\xa2\\xcd\\x72\\x41\\x25\\x6d' + :param data_bytes: data to hash :type data_bytes: Any :return: the script hash of the data diff --git a/boa3/builtin/interop/blockchain/__init__.py b/boa3/builtin/interop/blockchain/__init__.py index 47dc0af35..b5d1c99fc 100644 --- a/boa3/builtin/interop/blockchain/__init__.py +++ b/boa3/builtin/interop/blockchain/__init__.py @@ -28,6 +28,23 @@ def get_contract(hash: UInt160) -> Contract: """ Gets a contract with a given hash. + >>> get_contract(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) # GAS script hash + { + 'id': -6, + 'update_counter': 0, + 'hash': b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2', + 'nef': b'NEF3neo-core-v3.0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00#\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@QA\\xc7\\x9e', + 'manifest': { + 'name': 'GasToken', + 'group': [], + 'supported_standards': ['NEP-17'], + 'abi': [[['balanceOf', [['account', 20]], 17, 0, True], ['decimals', [], 17, 7, True], ['symbol', [], 19, 14, True], ['totalSupply', [], 17, 21, True], ['transfer', [['from', 20], ['to', 20], ['amount', 17], ['data', 0]], 16, 28, False]], [['Transfer', [['from', 20], ['to', 20], ['amount', 17]]]]], + 'permissions': [[None, None]], + 'trusts': [], + 'extras': 'null' + }, + } + :param hash: a smart contract hash :type hash: UInt160 :return: a contract @@ -42,6 +59,40 @@ def get_block(index_or_hash: Union[int, UInt256]) -> Block: """ Gets the block with the given index or hash. + >>> get_block(0) # first block + { + 'hash': b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04", + 'version': 0, + 'previous_hash': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'merkle_root': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'timestamp': 1468595301000, + 'nonce': 2083236893, + 'index': 0, + 'primary_index': 0, + 'next_consensus': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'transaction_count': 0, + } + + >>> get_block(UInt256(b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04")) # first block + { + 'hash': b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04", + 'version': 0, + 'previous_hash': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'merkle_root': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'timestamp': 1468595301000, + 'nonce': 2083236893, + 'index': 0, + 'primary_index': 0, + 'next_consensus': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'transaction_count': 0, + } + + >>> get_block(9999999) # block doesn't exist + None + + >>> get_block(UInt256(bytes(32))) # block doesn't exist + None + :param index_or_hash: index or hash identifier of the block :type index_or_hash: int or UInt256 :return: the desired block, if exists. None otherwise @@ -54,6 +105,21 @@ def get_transaction(hash_: UInt256) -> Transaction: """ Gets a transaction with the given hash. + >>> get_transaction(UInt256(b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr')) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + + >>> get_transaction(UInt256(bytes(32))) # transaction doesn't exist + None + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: the Transaction, if exists. None otherwise @@ -65,6 +131,30 @@ def get_transaction_from_block(block_hash_or_height: Union[UInt256, int], tx_ind """ Gets a transaction from a block. + >>> get_transaction_from_block(1, 0) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + + >>> get_transaction_from_block(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3'), 0) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + :param block_hash_or_height: a block identifier :type block_hash_or_height: UInt256 or int :param tx_index: the transaction identifier in the block @@ -78,6 +168,15 @@ def get_transaction_height(hash_: UInt256) -> int: """ Gets the height of a transaction. + >>> get_transaction_height(UInt256(b'\\x28\\x89\\x4f\\xb6\\x10\\x62\\x9d\\xea\\x4c\\xcd\\x00\\x2e\\x9e\\x11\\xa6\\xd0\\x3d\\x28\\x90\\xc0\\xe5\\xd4\\xfc\\x8f\\xc6\\x4f\\xcc\\x32\\x53\\xb5\\x48\\x01')) + 2108703 + + >>> get_transaction_height(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + 10 + + >>> get_transaction_height(UInt256(bytes(32))) # transaction doesn't exist + -1 + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: height of the transaction @@ -89,6 +188,17 @@ def get_transaction_signers(hash_: UInt256) -> List[Signer]: """ Gets the VM state of a transaction. + >>> get_transaction_signers(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + [ + { + "account": b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + "scopes": 1, + "allowed_contracts": [], + "allowed_groups": [], + "rules": [], + }, + ] + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: VM state of the transaction @@ -100,6 +210,9 @@ def get_transaction_vm_state(hash_: UInt256) -> VMState: """ Gets the VM state of a transaction. + >>> get_transaction_vm_state(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + VMState.HALT + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: VM state of the transaction @@ -111,6 +224,9 @@ def get_transaction_vm_state(hash_: UInt256) -> VMState: """ Gets the hash of the current block. +>>> current_hash +b'\\x3e\\x65\\xe5\\x4d\\x75\\x5a\\x94\\x90\\xd6\\x98\\x3a\\x77\\xe4\\x82\\xaf\\x7a\\x38\\xc9\\x8c\\x1a\\xc6\\xd9\\xda\\x48\\xbd\\x7c\\x22\\xb3\\x2a\\x9e\\x34\\xea' + :meta hide-value: """ @@ -118,5 +234,14 @@ def get_transaction_vm_state(hash_: UInt256) -> VMState: """ Gets the index of the current block. +>>> current_index +10908937 + +>>> current_index +2108690 + +>>> current_index +3529755 + :meta hide-value: """ diff --git a/boa3/builtin/interop/contract/__init__.py b/boa3/builtin/interop/contract/__init__.py index daf82a857..f33383ae2 100644 --- a/boa3/builtin/interop/contract/__init__.py +++ b/boa3/builtin/interop/contract/__init__.py @@ -2,6 +2,14 @@ 'CallFlags', 'Contract', 'ContractManifest', + 'ContractPermission', + 'ContractPermissionDescriptor', + 'ContractGroup', + 'ContractAbi', + 'ContractMethodDescriptor', + 'ContractEventDescriptor', + 'ContractParameterDefinition', + 'ContractParameterType', 'call_contract', 'create_contract', 'update_contract', @@ -19,7 +27,8 @@ from boa3.builtin.interop.contract.callflagstype import CallFlags from boa3.builtin.interop.contract.contract import Contract -from boa3.builtin.interop.contract.contractmanifest import ContractManifest +from boa3.builtin.interop.contract.contractmanifest import ContractManifest, ContractPermission, ContractPermissionDescriptor, \ + ContractGroup, ContractAbi, ContractMethodDescriptor, ContractEventDescriptor, ContractParameterDefinition, ContractParameterType from boa3.builtin.type import ECPoint, UInt160 @@ -27,6 +36,9 @@ def call_contract(script_hash: UInt160, method: str, args: Sequence = (), call_f """ Calls a smart contract given the method and the arguments. + >>> call_contract(NEO, 'balanceOf', UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) + 100 + :param script_hash: the target smart contract's script hash :type script_hash: UInt160 :param method: the name of the method to be executed @@ -49,6 +61,24 @@ def create_contract(nef_file: bytes, manifest: bytes, data: Any = None) -> Contr """ Creates a smart contract given the script and the manifest. + >>> nef_file_ = get_script(); manifest_ = get_manifest() # get the script and manifest somehow + ... create_contract(nef_file_, manifest_, None) # smart contract will be deployed + { + 'id': 2, + 'update_counter': 0, + 'hash': b'\\x92\\x8f+1q\\x86z_@\\x94\\xf5pE\\xcb\\xb8 \\x0f\\\\`Z', + 'nef': b'NEF3neo3-boa by COZ-_unit_tests_\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x07W\\x00\\x02xy\\x9e@\\xf9\\7b\\xbb\\xcc', + 'manifest': { + 'name': 'TestContract', + 'group': [], + 'supported_standards': [], + 'abi': [[['test', [['a', 17], ['b', 17]], 17, 0, False]], []], + 'permissions': [], + 'trusts': [], + 'extras': 'null' + }, + } + :param nef_file: the target smart contract's compiled nef :type nef_file: bytes :param manifest: the manifest.json that describes how the script should behave @@ -68,6 +98,10 @@ def update_contract(nef_file: bytes, manifest: bytes, data: Any = None): """ Updates the executing smart contract given the script and the manifest. + >>> nef_file_ = get_script(); manifest_ = get_manifest() # get the script and manifest somehow + ... update_contract(nef_file_, manifest_, None) # smart contract will be updated + None + :param nef_file: the new smart contract's compiled nef :type nef_file: bytes :param manifest: the new smart contract's manifest @@ -84,6 +118,10 @@ def update_contract(nef_file: bytes, manifest: bytes, data: Any = None): def destroy_contract(): """ Destroy the executing smart contract. + + >>> destroy_contract() + None + """ pass @@ -92,6 +130,9 @@ def get_minimum_deployment_fee() -> int: """ Gets the minimum fee of contract deployment. + >>> get_minimum_deployment_fee() + 1000000000 + :return: the minimum fee of contract deployment """ pass @@ -100,6 +141,9 @@ def get_minimum_deployment_fee() -> int: def get_call_flags() -> CallFlags: """ Gets the CallFlags in the current context. + + >>> get_call_flags() + CallFlags.READ_ONLY """ pass @@ -108,6 +152,9 @@ def create_standard_account(pub_key: ECPoint) -> UInt160: """ Calculates the script hash from a public key. + >>> create_standard_account(ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')) + b'\\r\\xa9g\\xa4\\x00C+\\xf2\\x7f\\x8e\\x8e\\xb4o\\xe8\\xace\\x9e\\xcc\\xde\\x04' + :param pub_key: the given public key :type pub_key: ECPoint @@ -121,6 +168,9 @@ def create_multisig_account(m: int, pub_keys: List[ECPoint]) -> UInt160: """ Calculates corresponding multisig account script hash for the given public keys. + >>> create_multisig_account(1, [ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')]) + b'"5,\\xd2\\x9e\\xe7\\xb4\\x02\\x08b\\xdbd\\x1e\\xedx\\x82\\x8fU(m' + :param m: the minimum number of correct signatures need to be provided in order for the verification to pass. :type m: int :param pub_keys: the public keys of the account @@ -136,6 +186,9 @@ def create_multisig_account(m: int, pub_keys: List[ECPoint]) -> UInt160: """ NEO's token script hash. +>>> NEO +b'\\xf5c\\xea@\\xbc(=M\\x0e\\x05\\xc4\\x8e\\xa3\\x05\\xb3\\xf2\\xa0s@\\xef' + :meta hide-value: """ @@ -143,5 +196,8 @@ def create_multisig_account(m: int, pub_keys: List[ECPoint]) -> UInt160: """ GAS' token script hash. +>>> GAS +b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2' + :meta hide-value: """ diff --git a/boa3/builtin/interop/crypto/__init__.py b/boa3/builtin/interop/crypto/__init__.py index db339357f..6816354b1 100644 --- a/boa3/builtin/interop/crypto/__init__.py +++ b/boa3/builtin/interop/crypto/__init__.py @@ -27,6 +27,12 @@ def sha256(key: Any) -> bytes: """ Encrypts a key using SHA-256. + >>> sha256('unit test') + b'\\xdau1>J\\xc2W\\xf8LN\\xfb2\\x0f\\xbd\\x01\\x1cr@<\\xf5\\x93<\\x90\\xd2\\xe3\\xb8$\\xd6H\\x96\\xf8\\x9a' + + >>> sha256(10) + b'\\x9c\\x82r\\x01\\xb9@\\x19\\xb4/\\x85pk\\xc4\\x9cY\\xff\\x84\\xb5`M\\x11\\xca\\xaf\\xb9\\n\\xb9HV\\xc4\\xe1\\xddz' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -39,6 +45,12 @@ def ripemd160(key: Any) -> bytes: """ Encrypts a key using RIPEMD-160. + >>> ripemd160('unit test') + b'H\\x8e\\xef\\xf4Zh\\x89:\\xe6\\xf1\\xdc\\x08\\xdd\\x8f\\x01\\rD\\n\\xbdH' + + >>> ripemd160(10) + b'\\xc0\\xda\\x02P8\\xed\\x83\\xc6\\x87\\xdd\\xc40\\xda\\x98F\\xec\\xb9\\x7f9\\x98' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -51,6 +63,12 @@ def hash160(key: Any) -> bytes: """ Encrypts a key using HASH160. + >>> hash160('unit test') + b'#Q\\xc9\\xaf+c\\x12\\xb1\\xb9\\x9e\\xa1\\x89t\\xa228g\\xec\\x0eF' + + >>> hash160(10) + b'\\x89\\x86D\\x19\\xa8\\xc3v%\\x00\\xfe\\x9a\\x98\\xaf\\x8f\\xbbO3u\\x08\\xf0' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -63,6 +81,12 @@ def hash256(key: Any) -> bytes: """ Encrypts a key using HASH256. + >>> hash256('unit test') + b'\\xdau1>J\\xc2W\\xf8LN\\xfb2\\x0f\\xbd\\x01\\x1cr@<\\xf5\\x93<\\x90\\xd2\\xe3\\xb8$\\xd6H\\x96\\xf8\\x9a' + + >>> hash256(10) + b'\\x9c\\x82r\\x01\\xb9@\\x19\\xb4/\\x85pk\\xc4\\x9cY\\xff\\x84\\xb5`M\\x11\\xca\\xaf\\xb9\\n\\xb9HV\\xc4\\xe1\\xddz' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -75,6 +99,10 @@ def check_sig(pub_key: ECPoint, signature: bytes) -> bool: """ Checks the signature for the current script container. + >>> check_sig(ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a'), + ... b'wrongsignature') + False + :param pub_key: the public key of the account :type pub_key: ECPoint :param signature: the signature of the current script container @@ -89,6 +117,11 @@ def check_multisig(pubkeys: List[ECPoint], signatures: List[bytes]) -> bool: """ Checks the signatures for the current script container. + >>> check_multisig([ECPoint(b"\\x03\\xcd\\xb0\\x67\\xd9\\x30\\xfd\\x5a\\xda\\xa6\\xc6\\x85\\x45\\x01\\x60\\x44\\xaa\\xdd\\xec\\x64\\xba\\x39\\xe5\\x48\\x25\\x0e\\xae\\xa5\\x51\\x17\\x2e\\x53\\x5c"), + ... ECPoint(b"\\x03l\\x841\\xccx\\xb31w\\xa6\\x0bK\\xcc\\x02\\xba\\xf6\\r\\x05\\xfe\\xe5\\x03\\x8es9\\xd3\\xa6\\x88\\xe3\\x94\\xc2\\xcb\\xd8C")], + ... [b'wrongsignature1', b'wrongsignature2']) + False + :param pubkeys: a list of public keys :type pubkeys: List[ECPoint] :param signatures: a list of signatures @@ -103,6 +136,10 @@ def verify_with_ecdsa(message: ByteString, pubkey: ECPoint, signature: ByteStrin """ Using the elliptic curve, it checks if the signature of the message was originally produced by the public key. + >>> verify_with_ecdsa('unit test', ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a'), + ... b'wrong_signature', NamedCurve.SECP256R1) + False + :param message: the encrypted message :type message: bytes :param pubkey: the public key that might have created the item @@ -121,6 +158,9 @@ def murmur32(data: ByteString, seed: int) -> ByteString: """ Computes the hash value for the specified byte array using the murmur32 algorithm. + >>> murmur32('unit test', 0) + b"\\x90D'G" + :param data: the input to compute the hash code for :type data: ByteString :param seed: the seed of the murmur32 hash function diff --git a/boa3/builtin/interop/iterator/__init__.py b/boa3/builtin/interop/iterator/__init__.py index cdecd2216..df3cc3b1a 100644 --- a/boa3/builtin/interop/iterator/__init__.py +++ b/boa3/builtin/interop/iterator/__init__.py @@ -27,6 +27,11 @@ def next(self) -> bool: """ Advances the iterator to the next element of the collection. + >>> from boa3.builtin.interop import storage + ... iterator = storage.find('prefix') + ... iterator.next() + True + :return: true if it advanced, false if there isn't a next element :rtype: bool """ diff --git a/boa3/builtin/interop/json/__init__.py b/boa3/builtin/interop/json/__init__.py index 6fe41e252..2accbe200 100644 --- a/boa3/builtin/interop/json/__init__.py +++ b/boa3/builtin/interop/json/__init__.py @@ -11,6 +11,9 @@ def json_serialize(item: Any) -> str: """ Serializes an item into a json. + >>> json_serialize({'one': 1, 'two': 2, 'three': 3}) + '{"one":1,"two":2,"three":3}' + :param item: The item that will be serialized :type item: Any :return: The serialized item @@ -26,6 +29,9 @@ def json_deserialize(json: str) -> Any: """ Deserializes a json into some valid type. + >>> json_deserialize('{"one":1,"two":2,"three":3}') + {'one': 1, 'three': 3, 'two': 2} + :param json: A json that will be deserialized :type json: str :return: The deserialized json diff --git a/boa3/builtin/interop/policy/__init__.py b/boa3/builtin/interop/policy/__init__.py index f246b6207..f5b9b60bc 100644 --- a/boa3/builtin/interop/policy/__init__.py +++ b/boa3/builtin/interop/policy/__init__.py @@ -14,6 +14,9 @@ def get_exec_fee_factor() -> int: Gets the execution fee factor. This is a multiplier that can be adjusted by the committee to adjust the system fees for transactions. + >>> get_exec_fee_factor() + 30 + :return: the execution fee factor :rtype: int """ @@ -24,6 +27,9 @@ def get_fee_per_byte() -> int: """ Gets the network fee per transaction byte. + >>> get_fee_per_byte() + 1000 + :return: the network fee per transaction byte :rtype: int """ @@ -34,6 +40,9 @@ def get_storage_price() -> int: """ Gets the storage price. + >>> get_storage_price() + 100000 + :return: the snapshot used to read data :rtype: int """ @@ -44,6 +53,9 @@ def is_blocked(account: UInt160) -> bool: """ Determines whether the specified account is blocked. + >>> is_blocked(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) + False + :param account: the account to be checked :type account: UInt160 diff --git a/boa3/builtin/interop/role/__init__.py b/boa3/builtin/interop/role/__init__.py index a4bfa7262..850fd14dd 100644 --- a/boa3/builtin/interop/role/__init__.py +++ b/boa3/builtin/interop/role/__init__.py @@ -12,6 +12,9 @@ def get_designated_by_role(role: Role, index: int) -> ECPoint: """ Gets the list of nodes for the specified role. + >>> get_designated_by_role(Role.ORACLE, 0) + [] + :param role: the type of the role :type role: Role :param index: the index of the block to be queried diff --git a/boa3/builtin/interop/runtime/__init__.py b/boa3/builtin/interop/runtime/__init__.py index f0c7eea11..43282ed7c 100644 --- a/boa3/builtin/interop/runtime/__init__.py +++ b/boa3/builtin/interop/runtime/__init__.py @@ -34,6 +34,12 @@ def check_witness(hash_or_pubkey: Union[UInt160, ECPoint]) -> bool: """ Verifies that the transactions or block of the calling contract has validated the required script hash. + >>> check_witness(calling_script_hash) + True + + >>> check_witness(UInt160(bytes(20))) + False + :param hash_or_pubkey: script hash or public key to validate :type hash_or_pubkey: UInt160 or ECPoint :return: a boolean value that represents whether the script hash was verified @@ -46,6 +52,14 @@ def notify(state: Any, notification_name: str = None): """ Notifies the client from the executing smart contract. + >>> var = 10 + ... notify(var) # An event will be triggered + None + + >>> var = 10 + ... notify(var, 'custom event name') # An event will be triggered + None + :param state: the notification message :type state: Any :param notification_name: name that'll be linked to the notification @@ -58,6 +72,9 @@ def log(message: str): """ Show log messages to the client from the executing smart contract. + >>> log('log sent') # An event that can be shown on the CLI + log sent + :param message: the log message :type message: str """ @@ -68,6 +85,9 @@ def get_trigger() -> TriggerType: """ Return the smart contract trigger type. + >>> get_trigger() + TriggerType.APPLICATION + :return: a value that represents the contract trigger type :rtype: TriggerType """ @@ -78,6 +98,26 @@ def get_notifications(script_hash: UInt160 = UInt160()) -> List[Notification]: """ This method gets current invocation notifications from specific 'script_hash'. + >>> notify(1); notify(2); notify(3) + ... get_notifications(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) + [ + [ + b'8\\xfe\\x11\\n\\xff7J\\xb8}\\xe9x6@\\xea\\x0b\\x00\\xf1|\\x82v', + 'notify', + [1] + ], + [ + b'8\\xfe\\x11\\n\\xff7J\\xb8}\\xe9x6@\\xea\\x0b\\x00\\xf1|\\x82v', + 'notify', + [2] + ], + [ + b'8\\xfe\\x11\\n\\xff7J\\xb8}\\xe9x6@\\xea\\x0b\\x00\\xf1|\\x82v', + 'notify', + [3] + ] + ] + :param script_hash: must have 20 bytes, but if it's all zero 0000...0000 it refers to all existing notifications (like a * wildcard) :type script_hash: UInt160 @@ -91,6 +131,9 @@ def get_network() -> int: """ Gets the magic number of the current network. + >>> get_network() + 860243278 + :return: the magic number of the current network :rtype: int """ @@ -101,6 +144,9 @@ def burn_gas(gas: int): """ Burns GAS to benefit the NEO ecosystem. + >>> burn_gas(1000) + None + :param gas: the amount of GAS that will be burned :type gas: int @@ -113,6 +159,15 @@ def get_random() -> int: """ Gets the next random number. + >>> get_random() + 191320526825634396960813166838892720709 + + >>> get_random() + 99083669484001682562631729023191545809 + + >>> get_random() + 328056213623902365838800581788496514419 + :return: the next random number :rtype: int """ @@ -122,6 +177,12 @@ def get_random() -> int: def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = CallFlags.NONE) -> Any: """ Loads a script at runtime. + + >>> from typing import cast + ... from boa3.builtin.vm import Opcode + ... cast(int, load_script(Opcode.ADD, [10, 2])) + 12 + """ pass @@ -130,6 +191,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the address version of the current network. +>>> address_version +53 + :meta hide-value: """ @@ -138,6 +202,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the script hash of the current context. +>>> executing_script_hash +b'^b]\\x90#\\xbf\\xcc\\x1f\\xd8\\x9e\\xe3\\xa4zd\\x14\\xa4\\xf0\\x96\\x9f`' + :meta hide-value: """ @@ -145,6 +212,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the script hash of the calling contract. +>>> calling_script_hash +b'\\x05\\x7f\\xc2\\x9d\\xba\\xb2\\xc1x\\xf5\\x81\\x83\\xbf\\xcb\\x87/\\xc3!\\xca\\xe1\\xd0' + :meta hide-value: """ @@ -152,6 +222,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the timestamp of the current block. +>>> time +1685395697108 + :meta hide-value: """ @@ -159,6 +232,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the remaining GAS that can be spent in order to complete the execution. +>>> gas_left +1999015490 + :meta hide-value: """ @@ -166,6 +242,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the name of the current platform. +>>> platform +'NEO' + :meta hide-value: """ @@ -173,6 +252,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the number of times the current contract has been called during the execution. +>>> invocation_counter +1 + :meta hide-value: """ @@ -180,6 +262,9 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the script hash of the entry context. +>>> entry_script_hash +b'\\tK\\xb31\\xa8\\x13\\x80`\\xad\\xf6\\xda\\xdf\\xc6R\\x9b\\xfdB\\xbf\\x83\\x8f' + :meta hide-value: """ @@ -187,5 +272,31 @@ def load_script(script: ByteString, args: Sequence = (), flags: CallFlags = Call """ Gets the current script container. +>>> script_container +[ + b'\\xf1y\\xc2\\xd6\\x1c\\xb6\\x98\\xa4\\xdc\\xf3\\xd67s\\xd7E\\xf0<;\\x98+\\xa2T\\x03P,T\\xe8\\xc6{ \\x101', + 0, + 442907905, + '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 0, + 0, + 0, + '' +] + +>>> script_container +[ + b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04", + 0, + '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 1468595301000, + 2083236893, + 0, + 0, + b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 0, +] + :meta hide-value: """ diff --git a/boa3/builtin/interop/stdlib/__init__.py b/boa3/builtin/interop/stdlib/__init__.py index ed358f431..ce21ef74f 100644 --- a/boa3/builtin/interop/stdlib/__init__.py +++ b/boa3/builtin/interop/stdlib/__init__.py @@ -23,6 +23,9 @@ def base58_encode(key: bytes) -> str: """ Encodes a bytes value using base58. + >>> base58_encode(b'unit test') + b"2VhL46g69A1mu" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -35,6 +38,9 @@ def base58_decode(key: str) -> bytes: """ Decodes a string value encoded with base58. + >>> base58_decode('2VhL46g69A1mu') + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded bytes @@ -48,6 +54,9 @@ def base58_check_encode(key: bytes) -> str: Converts a bytes value to its equivalent str representation that is encoded with base-58 digits. The encoded str contains the checksum of the binary data. + >>> base58_check_encode(b'unit test') + b"AnJcKqvgBwKxsjX75o" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -61,6 +70,9 @@ def base58_check_decode(key: str) -> bytes: Converts the specified str, which encodes binary data as base-58 digits, to an equivalent bytes value. The encoded str contains the checksum of the binary data. + >>> base58_check_decode('AnJcKqvgBwKxsjX75o') + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded bytes @@ -73,6 +85,9 @@ def base64_encode(key: bytes) -> str: """ Encodes a bytes value using base64. + >>> base64_encode(b'unit test') + b"dW5pdCB0ZXN0" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -85,6 +100,9 @@ def base64_decode(key: str) -> bytes: """ Decodes a string value encoded with base64. + >>> base64_decode("dW5pdCB0ZXN0") + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded bytes @@ -97,6 +115,18 @@ def serialize(item: Any) -> bytes: """ Serializes the given value into its bytes representation. + >>> serialize('42') + b'(\x0242' + + >>> serialize(42) + b'!\x01*' + + >>> serialize([2, 3, 5, 7]) + b'@\x04!\x01\x02!\x01\x03!\x01\x05!\x01\x07' + + >>> serialize({1: 1, 2: 1, 3: 2}) + b'H\x03!\x01\x01!\x01\x01!\x01\x02!\x01\x01!\x01\x03!\x01\x02' + :param item: value to be serialized :type item: Any :return: the serialized value @@ -111,6 +141,18 @@ def deserialize(data: bytes) -> Any: """ Deserializes the given bytes value. + >>> deserialize(b'(\x0242') + '42' + + >>> deserialize(b'!\x01*') + 42 + + >>> deserialize(b'@\x04!\x01\x02!\x01\x03!\x01\x05!\x01\x07') + [2, 3, 5, 7] + + >>> deserialize(b'H\x03!\x01\x01!\x01\x01!\x01\x02!\x01\x01!\x01\x03!\x01\x02') + {1: 1, 2: 1, 3: 2} + :param data: serialized value :type data: bytes :return: the deserialized result @@ -125,6 +167,18 @@ def atoi(value: str, base: int = 10) -> int: """ Converts a character string to a specific base value, decimal or hexadecimal. The default is decimal. + >>> atoi('10') + 10 + + >>> atoi('123') + 123 + + >>> atoi('1f', 16) + 31 + + >>> atoi('ff', 16) + -1 + :param value: the int value as a string :type value: str :param base: the value base @@ -141,6 +195,18 @@ def itoa(value: int, base: int = 10) -> str: """ Converts the specific type of value to a decimal or hexadecimal string. The default is decimal. + >>> itoa(10) + '10' + + >>> itoa(123) + '123' + + >>> itoa(-1, 16) + 'f' + + >>> itoa(15, 16) + '0f' + :param value: the int value :type value: int :param base: the value's base @@ -155,6 +221,12 @@ def memory_search(mem: ByteString, value: ByteString, start: int = 0, backward: """ Searches for a given value in a given memory. + >>> memory_search('abcde', 'a', 0) + 0 + + >>> memory_search('abcde', 'e', 0) + 4 + :param mem: the memory :type mem: bytes or str :param value: the value @@ -174,6 +246,15 @@ def memory_compare(mem1: ByteString, mem2: ByteString) -> int: """ Compares a memory with another one. + >>> memory_compare('abc', 'abc') + 0 + + >>> memory_compare('ABC', 'abc') + -1 + + >>> memory_compare('abc', 'ABC') + 1 + :param mem1: a memory to be compared to another one :type mem1: bytes or str :param mem2: a memory that will be compared with another one diff --git a/boa3/builtin/interop/storage/__init__.py b/boa3/builtin/interop/storage/__init__.py index ccf6d8bb4..298dcd2c4 100644 --- a/boa3/builtin/interop/storage/__init__.py +++ b/boa3/builtin/interop/storage/__init__.py @@ -24,6 +24,13 @@ def get(key: ByteString, context: StorageContext = None) -> bytes: """ Gets a value from the persistent store based on the given key. + >>> put('unit', 'test') + ... get('unit') + 'test' + + >>> get('fake_key') + '' + :param key: value identifier in the store :type key: str or bytes :param context: storage context to be used @@ -38,6 +45,9 @@ def get_context() -> StorageContext: """ Gets current storage context. + >>> get_context() # StorageContext cannot be read outside the blockchain + _InteropInterface + :return: the current storage context :rtype: StorageContext """ @@ -48,6 +58,9 @@ def get_read_only_context() -> StorageContext: """ Gets current read only storage context. + >>> get_context() # StorageContext cannot be read outside the blockchain + _InteropInterface + :return: the current read only storage context :rtype: StorageContext """ @@ -58,6 +71,9 @@ def put(key: ByteString, value: Union[int, ByteString], context: StorageContext """ Inserts a given value in the key-value format into the persistent storage. + >>> put('unit', 'test') + None + :param key: the identifier in the store for the new value :type key: str or bytes :param value: value to be stored @@ -72,6 +88,11 @@ def delete(key: ByteString, context: StorageContext = None): """ Removes a given key from the persistent storage if exists. + >>> put('unit', 'test') + ... delete() + ... get('unit') + '' + :param key: the identifier in the store for the new value :type key: str or bytes :param context: storage context to be used @@ -86,6 +107,17 @@ def find(prefix: ByteString, """ Searches in the storage for keys that start with the given prefix. + >>> put('a1', 'one') + ... put('a2', 'two') + ... put('a3', 'three') + ... put('b4', 'four') + ... findIterator = find('a') + ... findResults = [] + ... while findIterator.next(): + ... findResults.append(findIterator.value) + ... findResults + ['one', 'two', 'three'] + :param prefix: prefix to find the storage keys :type prefix: str or bytes :param context: storage context to be used diff --git a/boa3/builtin/math.py b/boa3/builtin/math.py index 114963c3c..8f8765ccf 100644 --- a/boa3/builtin/math.py +++ b/boa3/builtin/math.py @@ -3,6 +3,9 @@ def ceil(x: int, decimals: int) -> int: Return the ceiling of x given the amount of decimals. This is the smallest integer >= x. + >>> ceil(12345, 3) + 13000 + :param x: any integer number :type x: int :param decimals: number of decimals @@ -20,6 +23,9 @@ def floor(x: int, decimals: int) -> int: Return the floor of x given the amount of decimals. This is the largest integer <= x. + >>> floor(12345, 3) + 12000 + :param x: any integer number :type x: int :param decimals: number of decimals @@ -36,6 +42,15 @@ def sqrt(x: int) -> int: """ Gets the square root of a number. + >>> sqrt(1) + 1 + + >>> sqrt(10) + 3 + + >>> sqrt(25) + 5 + :param x: a non-negative number :type x: int :return: the square root of a number diff --git a/boa3/builtin/nativecontract/contractmanagement.py b/boa3/builtin/nativecontract/contractmanagement.py index f6e554ff4..11b03f6fe 100644 --- a/boa3/builtin/nativecontract/contractmanagement.py +++ b/boa3/builtin/nativecontract/contractmanagement.py @@ -22,6 +22,9 @@ def get_minimum_deployment_fee(cls) -> int: """ Gets the minimum fee of contract deployment. + >>> ContractManagement.get_minimum_deployment_fee() + 1000000000 + :return: the minimum fee of contract deployment """ pass @@ -31,6 +34,23 @@ def get_contract(cls, script_hash: UInt160) -> Contract: """ Gets a contract with a given hash. + >>> ContractManagement.get_contract(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) # GAS script hash + { + 'id': -6, + 'update_counter': 0, + 'hash': b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2', + 'nef': b'NEF3neo-core-v3.0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00#\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@\\x10A\\x1a\\xf7{g@QA\\xc7\\x9e', + 'manifest': { + 'name': 'GasToken', + 'group': [], + 'supported_standards': ['NEP-17'], + 'abi': [[['balanceOf', [['account', 20]], 17, 0, True], ['decimals', [], 17, 7, True], ['symbol', [], 19, 14, True], ['totalSupply', [], 17, 21, True], ['transfer', [['from', 20], ['to', 20], ['amount', 17], ['data', 0]], 16, 28, False]], [['Transfer', [['from', 20], ['to', 20], ['amount', 17]]]]], + 'permissions': [[None, None]], + 'trusts': [], + 'extras': 'null' + }, + } + :param script_hash: a smart contract hash :type script_hash: UInt160 :return: a contract @@ -45,6 +65,18 @@ def has_method(cls, hash: UInt160, method: str, parameter_count: int) -> bool: """ Check if a method exists in a contract. + >>> ContractManagement.has_method(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2'), + ... 'balanceOf', 1) # GAS script hash + True + + >>> ContractManagement.has_method(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2'), + ... 'balanceOf', 10) # GAS script hash + False + + >>> ContractManagement.has_method(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2'), + ... 'invalid', 1) # GAS script hash + False + :param hash: The hash of the deployed contract :type hash: UInt160 :param method: The name of the method @@ -63,6 +95,24 @@ def deploy(cls, nef_file: bytes, manifest: bytes, data: Any = None) -> Contract: """ Creates a smart contract given the script and the manifest. + >>> nef_file_ = get_script(); manifest_ = get_manifest() # get the script and manifest somehow + ... ContractManagement.deploy(nef_file_, manifest_, None) # smart contract will be deployed + { + 'id': 2, + 'update_counter': 0, + 'hash': b'\\x92\\x8f+1q\\x86z_@\\x94\\xf5pE\\xcb\\xb8 \\x0f\\\\`Z', + 'nef': b'NEF3neo3-boa by COZ-_unit_tests_\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x07W\\x00\\x02xy\\x9e@\\xf9\\7b\\xbb\\xcc', + 'manifest': { + 'name': 'TestContract', + 'group': [], + 'supported_standards': [], + 'abi': [[['test', [['a', 17], ['b', 17]], 17, 0, False]], []], + 'permissions': [], + 'trusts': [], + 'extras': 'null' + }, + } + :param nef_file: the target smart contract's compiled nef :type nef_file: bytes :param manifest: the manifest.json that describes how the script should behave @@ -82,6 +132,10 @@ def update(cls, nef_file: bytes, manifest: bytes, data: Any = None): """ Updates the executing smart contract given the script and the manifest. + >>> nef_file_ = get_script(); manifest_ = get_manifest() # get the script and manifest somehow + ... ContractManagement.update(nef_file_, manifest_, None) # smart contract will be updated + None + :param nef_file: the new smart contract's compiled nef :type nef_file: bytes :param manifest: the new smart contract's manifest @@ -98,5 +152,9 @@ def update(cls, nef_file: bytes, manifest: bytes, data: Any = None): def destroy(cls): """ Destroy the executing smart contract. + + >>> destroy_contract() + None + """ pass diff --git a/boa3/builtin/nativecontract/cryptolib.py b/boa3/builtin/nativecontract/cryptolib.py index cb5fa0d7f..d8160aaae 100644 --- a/boa3/builtin/nativecontract/cryptolib.py +++ b/boa3/builtin/nativecontract/cryptolib.py @@ -21,6 +21,9 @@ def murmur32(cls, data: ByteString, seed: int) -> ByteString: """ Computes the hash value for the specified byte array using the murmur32 algorithm. + >>> CryptoLib.murmur32('unit test', 0) + b"\\x90D'G" + :param data: the input to compute the hash code for :type data: ByteString :param seed: the seed of the murmur32 hash function @@ -35,6 +38,12 @@ def sha256(cls, key: Any) -> bytes: """ Encrypts a key using SHA-256. + >>> CryptoLib.sha256('unit test') + b'\\xdau1>J\\xc2W\\xf8LN\\xfb2\\x0f\\xbd\\x01\\x1cr@<\\xf5\\x93<\\x90\\xd2\\xe3\\xb8$\\xd6H\\x96\\xf8\\x9a' + + >>> CryptoLib.sha256(10) + b'\\x9c\\x82r\\x01\\xb9@\\x19\\xb4/\\x85pk\\xc4\\x9cY\\xff\\x84\\xb5`M\\x11\\xca\\xaf\\xb9\\n\\xb9HV\\xc4\\xe1\\xddz' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -47,6 +56,12 @@ def ripemd160(cls, key: Any) -> bytes: """ Encrypts a key using RIPEMD-160. + >>> CryptoLib.ripemd160('unit test') + b'H\\x8e\\xef\\xf4Zh\\x89:\\xe6\\xf1\\xdc\\x08\\xdd\\x8f\\x01\\rD\\n\\xbdH' + + >>> CryptoLib.ripemd160(10) + b'\\xc0\\xda\\x02P8\\xed\\x83\\xc6\\x87\\xdd\\xc40\\xda\\x98F\\xec\\xb9\\x7f9\\x98' + :param key: the key to be encrypted :type key: Any :return: a byte value that represents the encrypted key @@ -59,6 +74,10 @@ def verify_with_ecdsa(cls, message: ByteString, pubkey: ECPoint, signature: Byte """ Using the elliptic curve, it checks if the signature of the message was originally produced by the public key. + >>> CryptoLib.verify_with_ecdsa('unit test', ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a'), + ... b'wrong_signature', NamedCurve.SECP256R1) + False + :param message: the encrypted message :type message: bytes :param pubkey: the public key that might have created the item diff --git a/boa3/builtin/nativecontract/gas.py b/boa3/builtin/nativecontract/gas.py index f4fffce5f..279717c88 100644 --- a/boa3/builtin/nativecontract/gas.py +++ b/boa3/builtin/nativecontract/gas.py @@ -20,6 +20,9 @@ def symbol(cls) -> str: """ Gets the symbol of GAS. + >>> GAS.symbol() + 'GAS' + :return: the GAS string. :rtype: str """ @@ -30,6 +33,9 @@ def decimals(cls) -> int: """ Gets the amount of decimals used by GAS. + >>> GAS.decimals() + 8 + :return: the number 8. :rtype: int """ @@ -40,6 +46,12 @@ def totalSupply(cls) -> int: """ Gets the total token supply deployed in the system. + >>> GAS.totalSupply() + 5199999098939320 + + >>> GAS.totalSupply() + 5522957322800300 + :return: the total token supply deployed in the system. :rtype: int """ @@ -50,6 +62,12 @@ def balanceOf(cls, account: UInt160) -> int: """ Get the current balance of an address. + >>> GAS.balanceOf(UInt160(bytes(20))) + 0 + + >>> GAS.balanceOf(UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6')) + 1000000000 + :param account: the account's address to retrieve the balance for :type account: UInt160 :return: the account's balance @@ -65,6 +83,21 @@ def transfer(cls, from_address: UInt160, to_address: UInt160, amount: int, data: If the method succeeds, it will fire the `Transfer` event and must return true, even if the amount is 0, or from and to are the same address. + >>> GAS.transfer(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), # this script hash needs to have signed the transaction or block + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... 10000, None) + True + + >>> GAS.transfer(UInt160(bytes(20)), + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... 10000, None) + False + + >>> GAS.transfer(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... -1, None) + False + :param from_address: the address to transfer from :type from_address: UInt160 :param to_address: the address to transfer to diff --git a/boa3/builtin/nativecontract/ledger.py b/boa3/builtin/nativecontract/ledger.py index 0ec692979..3866338c8 100644 --- a/boa3/builtin/nativecontract/ledger.py +++ b/boa3/builtin/nativecontract/ledger.py @@ -21,6 +21,37 @@ def get_block(cls, index_or_hash: Union[int, UInt256]) -> Block: """ Gets the block with the given index or hash. + >>> Ledger.get_block(0) # first block + { + 'hash': b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04", + 'version': 0, + 'previous_hash': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'merkle_root': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'timestamp': 1468595301000, + 'nonce': 2083236893, + 'index': 0, + 'primary_index': 0, + 'next_consensus': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'transaction_count': 0, + } + + >>> Ledger.get_block(UInt256(b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04")) # first block + { + 'hash': b"S{\\xed'\\x85&\\xf5\\x93U=\\xc1\\xbf'\\x95\\xc4/\\x80X\\xdb\\xd5\\xa1-\\x97q\\x85\\xe3I\\xe5\\x99cd\\x04", + 'version': 0, + 'previous_hash': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'merkle_root': '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', + 'timestamp': 1468595301000, + 'nonce': 2083236893, + 'index': 0, + 'primary_index': 0, + 'next_consensus': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'transaction_count': 0, + } + + >>> Ledger.get_block(9999999) # block doesn't exist + None + :param index_or_hash: index or hash identifier of the block :type index_or_hash: int or UInt256 :return: the desired block, if exists. None otherwise @@ -33,6 +64,15 @@ def get_current_index(cls) -> int: """ Gets the index of the current block. + >>> Ledger.get_current_index() + 10908937 + + >>> Ledger.get_current_index() + 2108690 + + >>> Ledger.get_current_index() + 3529755 + :return: the index of the current block :rtype: int """ @@ -43,6 +83,21 @@ def get_transaction(cls, hash_: UInt256) -> Transaction: """ Gets a transaction with the given hash. + >>> Ledger.get_transaction(UInt256(b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr')) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + + >>> Ledger.get_transaction(UInt256(bytes(32))) # transaction doesn't exist + None + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: the Transaction, if exists. None otherwise @@ -54,6 +109,30 @@ def get_transaction_from_block(cls, block_hash_or_height: Union[UInt256, int], t """ Gets a transaction from a block. + >>> Ledger.get_transaction_from_block(1, 0) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + + >>> Ledger.get_transaction_from_block(UInt256(b'\\x21|\\xc2~U\\t\\x89^\\x0c\\xc0\\xd29wl\\x0b\\xad d\\xe1\\xf5U\\xd7\\xf5B\\xa5/\\x8b\\x8f\\x8b\\x22\\x24\\x80'), 0) + { + 'hash': b'\\xff\\x7f\\x18\\x99\\x8c\\x1d\\x10X{bA\\xc2\\xe3\\xdf\\xc8\\xb0\\x9f>\\xd0\\xd2G\\xe3\\xba\\xd8\\x96\\xb9\\x0e\\xc1iS\\xcdr', + 'version': 0, + 'nonce': 2025056010, + 'sender': b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + 'system_fee': 2028330, + 'network_fee': 1206580, + 'valid_until_block': 5761, + 'script': b'\\x0c\\x14\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04\\x11\\xc0\\x1f\\x0c\\tbalanceOf\\x0c\\x14\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2Ab}[R', + } + :param block_hash_or_height: a block identifier :type block_hash_or_height: UInt256 or int :param tx_index: the transaction identifier in the block @@ -67,6 +146,15 @@ def get_transaction_height(cls, hash_: UInt256) -> int: """ Gets the height of a transaction. + >>> Ledger.get_transaction_height(UInt256(b'\\x28\\x89\\x4f\\xb6\\x10\\x62\\x9d\\xea\\x4c\\xcd\\x00\\x2e\\x9e\\x11\\xa6\\xd0\\x3d\\x28\\x90\\xc0\\xe5\\xd4\\xfc\\x8f\\xc6\\x4f\\xcc\\x32\\x53\\xb5\\x48\\x01')) + 2108703 + + >>> Ledger.get_transaction_height(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + 10 + + >>> Ledger.get_transaction_height(UInt256(bytes(32))) # transaction doesn't exist + -1 + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: height of the transaction @@ -78,6 +166,17 @@ def get_transaction_signers(cls, hash_: UInt256) -> List[Signer]: """ Gets the VM state of a transaction. + >>> Ledger.get_transaction_signers(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + [ + { + "account": b'\\xa6\\xea\\xb0\\xae\\xaf\\xb4\\x96\\xa1\\x1b\\xb0|\\x88\\x17\\xcar\\xa5J\\x00\\x12\\x04', + "scopes": 1, + "allowed_contracts": [], + "allowed_groups": [], + "rules": [], + }, + ] + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: VM state of the transaction @@ -89,6 +188,9 @@ def get_transaction_vm_state(cls, hash_: UInt256) -> VMState: """ Gets the VM state of a transaction. + >>> Ledger.get_transaction_vm_state(UInt256(b'\\x29\\x41\\x06\\xdb\\x4c\\xf3\\x84\\xa7\\x20\\x4d\\xba\\x0a\\x04\\x03\\x72\\xb3\\x27\\x76\\xf2\\x6e\\xd3\\x87\\x49\\x88\\xd0\\x3e\\xff\\x5d\\xa9\\x93\\x8c\\xa3')) + VMState.HALT + :param hash_: hash identifier of the transaction :type hash_: UInt256 :return: VM state of the transaction diff --git a/boa3/builtin/nativecontract/neo.py b/boa3/builtin/nativecontract/neo.py index 6604ed663..a60295167 100644 --- a/boa3/builtin/nativecontract/neo.py +++ b/boa3/builtin/nativecontract/neo.py @@ -22,6 +22,9 @@ def symbol(cls) -> str: """ Gets the symbol of NEO. + >>> NEO.symbol() + 'NEO' + :return: the NEO string. :rtype: str """ @@ -32,7 +35,10 @@ def decimals(cls) -> int: """ Gets the amount of decimals used by NEO. - :return: the number 8. + >>> NEO.decimals() + 0 + + :return: the number 0. :rtype: int """ pass @@ -42,6 +48,9 @@ def totalSupply(cls) -> int: """ Gets the total token supply deployed in the system. + >>> NEO.totalSupply() + 100000000 + :return: the total token supply deployed in the system. :rtype: int """ @@ -52,6 +61,12 @@ def balanceOf(cls, account: UInt160) -> int: """ Get the current balance of an address. + >>> NEO.balanceOf(UInt160(bytes(20))) + 0 + + >>> NEO.balanceOf(UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6')) + 100 + :param account: the account's address to retrieve the balance for :type account: UInt160 :return: the account's balance @@ -67,6 +82,22 @@ def transfer(cls, from_address: UInt160, to_address: UInt160, amount: int, data: If the method succeeds, it will fire the `Transfer` event and must return true, even if the amount is 0, or from and to are the same address. + >>> NEO.transfer(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), # this script hash needs to have signed the transaction or block + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... 10, None) + True + + >>> NEO.transfer(UInt160(bytes(20)), + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... 10, None) + False + + >>> NEO.transfer(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), + ... UInt160(b'\\xabv\\xe2\\xcb\\xb0\\x16,vG\\x2f\\x44Va\\x10\\x14\\x19\\xf3\\xff\\xa1\\xe6'), + ... -1, None) + False + + :param from_address: the address to transfer from :type from_address: UInt160 :param to_address: the address to transfer to @@ -87,6 +118,9 @@ def get_gas_per_block(cls) -> int: """ Gets the amount of GAS generated in each block. + >>> NEO.get_gas_per_block() + 500000000 + :return: the amount of GAS generated :rtype: int """ @@ -97,6 +131,12 @@ def unclaimed_gas(cls, account: UInt160, end: int) -> int: """ Gets the amount of unclaimed GAS in the specified account. + >>> NEO.unclaimed_gas(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), 0) + 100000000 + + >>> NEO.unclaimed_gas(UInt160(bytes(20), 0) + 100000000 + :param account: the account to check :type account: UInt160 :param end: the block index used when calculating GAS @@ -109,6 +149,9 @@ def register_candidate(cls, pubkey: ECPoint) -> bool: """ Registers as a candidate. + >>> NEO.register_candidate(ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')) + False + :param pubkey: The public key of the account to be registered :type pubkey: ECPoint :return: whether the registration was a success or not @@ -121,6 +164,9 @@ def unregister_candidate(cls, pubkey: ECPoint) -> bool: """ Unregisters as a candidate. + >>> NEO.unregister_candidate(ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')) + False + :param pubkey: The public key of the account to be unregistered :type pubkey: ECPoint :return: whether the unregistration was a success or not @@ -133,6 +179,9 @@ def vote(cls, account: UInt160, vote_to: ECPoint) -> bool: """ Votes for a candidate. + >>> NEO.vote(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13'), ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')) + False + :param account: the account that is voting :type account: UInt160 :param vote_to: the public key of the one being voted @@ -145,6 +194,9 @@ def get_all_candidates(cls) -> Iterator: """ Gets the registered candidates iterator. + >>> NEO.get_all_candidates() + [] + :return: all registered candidates :rtype: Iterator """ @@ -155,6 +207,9 @@ def un_vote(cls, account: UInt160) -> bool: """ Removes the vote of the candidate voted. It would be the same as calling vote(account, None). + >>> NEO.un_vote(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13')) + False + :param account: the account that is removing the vote :type account: UInt160 """ @@ -165,6 +220,9 @@ def get_candidates(cls) -> List[Tuple[ECPoint, int]]: """ Gets the list of all registered candidates. + >>> NEO.get_candidates() + [] + :return: all registered candidates :rtype: List[Tuple[ECPoint, int]] """ @@ -175,6 +233,12 @@ def get_candidate_vote(cls, pubkey: ECPoint) -> int: """ Gets votes from specific candidate. + >>> NEO.get_candidate_vote(ECPoint(b'\\x03\\x5a\\x92\\x8f\\x20\\x16\\x39\\x20\\x4e\\x06\\xb4\\x36\\x8b\\x1a\\x93\\x36\\x54\\x62\\xa8\\xeb\\xbf\\xf0\\xb8\\x81\\x81\\x51\\xb7\\x4f\\xaa\\xb3\\xa2\\xb6\\x1a')) + 100 + + >>> NEO.get_candidate_vote(ECPoint(bytes(32))) + -1 + :return: Votes or -1 if it was not found. :rtype: int """ @@ -185,6 +249,9 @@ def get_committee(cls) -> List[ECPoint]: """ Gets all committee members list. + >>> NEO.get_committee() + [ b'\\x02|\\x84\\xb0V\\xc2j{$XG\\x1em\\xcfgR\\xed\\xd9k\\x96\\x88}x34\\xe3Q\\xdd\\xfe\\x13\\xc4\\xbc\\xa2' ] + :return: all committee members :rtype: List[ECPoint] """ @@ -195,6 +262,9 @@ def get_next_block_validators(cls) -> List[ECPoint]: """ Gets validators list of the next block. + >>> NEO.get_next_block_validators() + [ b'\\x02|\\x84\\xb0V\\xc2j{$XG\\x1em\\xcfgR\\xed\\xd9k\\x96\\x88}x34\\xe3Q\\xdd\\xfe\\x13\\xc4\\xbc\\xa2' ] + :return: the public keys of the validators :rtype: List[ECPoint] """ @@ -205,6 +275,13 @@ def get_account_state(cls, account: UInt160) -> NeoAccountState: """ Gets the latest votes of the specified account. + >>> NEO.get_account_state(UInt160(b'\\xc9F\\x17\\xba!\\x99\\x07\\xc1\\xc5\\xd6\t#\\xe1\\x9096\\x89U\\xac\\x13')) + { + 'balance': 100, + 'height': 2, + 'vote_to': None, + } + :param account: the specified account :type account: UInt160 :return: the state of the account diff --git a/boa3/builtin/nativecontract/oracle.py b/boa3/builtin/nativecontract/oracle.py index 8922b4eb6..83e9acf5a 100644 --- a/boa3/builtin/nativecontract/oracle.py +++ b/boa3/builtin/nativecontract/oracle.py @@ -23,6 +23,10 @@ def request(cls, url: str, request_filter: Union[str, None], callback: str, user This method just requests data from the oracle, it won't return the result. + >>> Oracle.request('https://dora.coz.io/api/v1/neo3/testnet/asset/0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5', + ... '', 'callback_name', None, 10 * 10 ** 8) + None + :param url: External url to retrieve the data :type url: str :param request_filter: @@ -56,6 +60,9 @@ def get_price(cls) -> int: """ Gets the price for an Oracle request. + >>> Oracle.get_price() + 50000000 + :return: the price for an Oracle request """ pass diff --git a/boa3/builtin/nativecontract/policy.py b/boa3/builtin/nativecontract/policy.py index 88198d95b..9715dca54 100644 --- a/boa3/builtin/nativecontract/policy.py +++ b/boa3/builtin/nativecontract/policy.py @@ -18,6 +18,9 @@ def get_fee_per_byte(cls) -> int: """ Gets the network fee per transaction byte. + >>> Policy.get_fee_per_byte() + 1000 + :return: the network fee per transaction byte :rtype: int """ @@ -29,6 +32,9 @@ def get_exec_fee_factor(cls) -> int: Gets the execution fee factor. This is a multiplier that can be adjusted by the committee to adjust the system fees for transactions. + >>> Policy.get_exec_fee_factor() + 30 + :return: the execution fee factor :rtype: int """ @@ -39,6 +45,9 @@ def get_storage_price(cls) -> int: """ Gets the storage price. + >>> Policy.get_storage_price() + 100000 + :return: the snapshot used to read data :rtype: int """ @@ -49,6 +58,9 @@ def is_blocked(cls, account: UInt160) -> bool: """ Determines whether the specified account is blocked. + >>> Policy.is_blocked(UInt160(b'\\xcfv\\xe2\\x8b\\xd0\\x06,JG\\x8e\\xe3Ua\\x01\\x13\\x19\\xf3\\xcf\\xa4\\xd2')) + False + :param account: the account to be checked :type account: UInt160 diff --git a/boa3/builtin/nativecontract/rolemanagement.py b/boa3/builtin/nativecontract/rolemanagement.py index f0be0046c..46662d0ca 100644 --- a/boa3/builtin/nativecontract/rolemanagement.py +++ b/boa3/builtin/nativecontract/rolemanagement.py @@ -20,6 +20,9 @@ def get_designated_by_role(cls, role: Role, index: int) -> ECPoint: """ Gets the list of nodes for the specified role. + >>> get_designated_by_role(Role.ORACLE, 0) + [] + :param role: the type of the role :type role: Role :param index: the index of the block to be queried diff --git a/boa3/builtin/nativecontract/stdlib.py b/boa3/builtin/nativecontract/stdlib.py index 9583e5fc4..f20f671e8 100644 --- a/boa3/builtin/nativecontract/stdlib.py +++ b/boa3/builtin/nativecontract/stdlib.py @@ -20,6 +20,18 @@ def serialize(cls, item: Any) -> bytes: """ Serializes the given value into its bytes representation. + >>> StdLib.serialize('42') + b'(\\x0242' + + >>> StdLib.serialize(42) + b'!\\x01*' + + >>> StdLib.serialize([2, 3, 5, 7]) + b'@\\x04!\\x01\\x02!\\x01\\x03!\\x01\\x05!\\x01\\x07' + + >>> StdLib.serialize({1: 1, 2: 1, 3: 2}) + b'H\\x03!\\x01\\x01!\\x01\\x01!\\x01\\x02!\\x01\\x01!\\x01\\x03!\\x01\\x02' + :param item: value to be serialized :type item: Any :return: the serialized value @@ -34,6 +46,18 @@ def deserialize(cls, data: bytes) -> Any: """ Deserializes the given bytes value. + >>> StdLib.deserialize(b'(\\x0242') + '42' + + >>> StdLib.deserialize(b'!\\x01*') + 42 + + >>> StdLib.deserialize(b'@\\x04!\\x01\\x02!\\x01\\x03!\\x01\\x05!\\x01\\x07') + [2, 3, 5, 7] + + >>> StdLib.deserialize(b'H\\x03!\\x01\\x01!\\x01\\x01!\\x01\\x02!\\x01\\x01!\\x01\\x03!\\x01\\x02') + {1: 1, 2: 1, 3: 2} + :param data: serialized value :type data: bytes :return: the deserialized result @@ -48,6 +72,9 @@ def json_serialize(cls, item: Any) -> str: """ Serializes an item into a json. + >>> StdLib.json_serialize({'one': 1, 'two': 2, 'three': 3}) + '{"one":1,"two":2,"three":3}' + :param item: The item that will be serialized :type item: Any :return: The serialized item @@ -63,6 +90,9 @@ def json_deserialize(cls, json: str) -> Any: """ Deserializes a json into some valid type. + >>> StdLib.json_deserialize('{"one":1,"two":2,"three":3}') + {'one': 1, 'three': 3, 'two': 2} + :param json: A json that will be deserialized :type json: str :return: The deserialized json @@ -77,6 +107,9 @@ def base64_decode(cls, key: str) -> bytes: """ Decodes a string value encoded with base64. + >>> StdLib.base64_decode("dW5pdCB0ZXN0") + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded string @@ -89,6 +122,9 @@ def base64_encode(cls, key: bytes) -> str: """ Encodes a bytes value using base64. + >>> StdLib.base64_encode(b'unit test') + b"dW5pdCB0ZXN0" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -101,6 +137,9 @@ def base58_decode(cls, key: str) -> bytes: """ Decodes a string value encoded with base58. + >>> StdLib.base58_decode('2VhL46g69A1mu') + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded bytes @@ -113,6 +152,9 @@ def base58_encode(cls, key: bytes) -> str: """ Encodes a bytes value using base58. + >>> StdLib.base58_encode(b'unit test') + b"2VhL46g69A1mu" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -126,6 +168,9 @@ def base58_check_decode(cls, key: str) -> bytes: Converts the specified str, which encodes binary data as base-58 digits, to an equivalent bytes value. The encoded str contains the checksum of the binary data. + >>> StdLib.base58_check_decode('AnJcKqvgBwKxsjX75o') + b"unit test" + :param key: string value to be decoded :type key: str :return: the decoded bytes @@ -139,6 +184,9 @@ def base58_check_encode(cls, key: bytes) -> str: Converts a bytes value to its equivalent str representation that is encoded with base-58 digits. The encoded str contains the checksum of the binary data. + >>> StdLib.base58_check_encode(b'unit test') + b"AnJcKqvgBwKxsjX75o" + :param key: bytes value to be encoded :type key: bytes :return: the encoded string @@ -151,6 +199,18 @@ def itoa(cls, value: int, base: int = 10) -> str: """ Converts the specific type of value to a decimal or hexadecimal string. The default is decimal. + >>> StdLib.itoa(10) + '10' + + >>> StdLib.itoa(123) + '123' + + >>> StdLib.itoa(-1, 16) + 'f' + + >>> StdLib.itoa(15, 16) + '0f' + :param value: the int value :type value: int :param base: the value's base @@ -165,6 +225,18 @@ def atoi(cls, value: str, base: int = 10) -> int: """ Converts a character string to a specific base value, decimal or hexadecimal. The default is decimal. + >>> StdLib.atoi('10') + 10 + + >>> StdLib.atoi('123') + 123 + + >>> StdLib.atoi('1f', 16) + 31 + + >>> StdLib.atoi('ff', 16) + -1 + :param value: the int value as a string :type value: str :param base: the value base @@ -181,6 +253,15 @@ def memory_compare(cls, mem1: ByteString, mem2: ByteString) -> int: """ Compares a memory with another one. + >>> StdLib.memory_compare('abc', 'abc') + 0 + + >>> StdLib.memory_compare('ABC', 'abc') + -1 + + >>> StdLib.memory_compare('abc', 'ABC') + 1 + :param mem1: a memory to be compared to another one :type mem1: bytes or str :param mem2: a memory that will be compared with another one @@ -196,6 +277,12 @@ def memory_search(cls, mem: ByteString, value: ByteString, start: int = 0, backw """ Searches for a given value in a given memory. + >>> StdLib.memory_search('abcde', 'a', 0) + 0 + + >>> StdLib.memory_search('abcde', 'e', 0) + 4 + :param mem: the memory :type mem: bytes or str :param value: the value diff --git a/boa3/builtin/type/__init__.py b/boa3/builtin/type/__init__.py index dc8d94902..ffa15c845 100644 --- a/boa3/builtin/type/__init__.py +++ b/boa3/builtin/type/__init__.py @@ -20,6 +20,15 @@ class Event: """ Describes an action that happened in the blockchain. + + >>> from boa3.builtin.compile_time import CreateNewEvent + ... new_event: Event = CreateNewEvent( # create a new Event with the CreateNewEvent method + ... [ + ... ('name', str), + ... ('amount', int) + ... ], + ... 'New Event' + ... ) """ def __call__(self, *args, **kwargs): diff --git a/docs/source/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.rst b/docs/source/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.rst index ae9d7c008..a31848456 100644 --- a/docs/source/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.rst +++ b/docs/source/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.rst @@ -2,13 +2,3 @@ blockchain ========== .. automodule:: boa3.builtin.interop.blockchain - -Block ------ - -.. automodule:: boa3.builtin.interop.blockchain.block - -Transaction ------------ - -.. automodule:: boa3.builtin.interop.blockchain.transaction diff --git a/docs/source/boa3/builtin/interop/boa3-builtin-interop.rst b/docs/source/boa3/builtin/interop/boa3-builtin-interop.rst index 6ed10c10c..cc4df47ea 100644 --- a/docs/source/boa3/builtin/interop/boa3-builtin-interop.rst +++ b/docs/source/boa3/builtin/interop/boa3-builtin-interop.rst @@ -13,6 +13,9 @@ Subpackages crypto/boa3-builtin-interop-crypto iterator/boa3-builtin-interop-iterator json/boa3-builtin-interop-json + oracle/boa3-builtin-interop-oracle + policy/boa3-builtin-interop-policy + role/boa3-builtin-interop-role runtime/boa3-builtin-interop-runtime stdlib/boa3-builtin-interop-stdlib storage/boa3-builtin-interop-storage diff --git a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-callflags.rst b/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-callflags.rst deleted file mode 100644 index a4aef5443..000000000 --- a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-callflags.rst +++ /dev/null @@ -1,4 +0,0 @@ -callflagstype -------------- - -.. automodule:: boa3.builtin.interop.contract.callflagstype diff --git a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contract.rst b/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contract.rst deleted file mode 100644 index 5f588f2ee..000000000 --- a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contract.rst +++ /dev/null @@ -1,4 +0,0 @@ -contract --------- - -.. automodule:: boa3.builtin.interop.contract.contract diff --git a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contractmanifest.rst b/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contractmanifest.rst deleted file mode 100644 index d2b4006cb..000000000 --- a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract-contractmanifest.rst +++ /dev/null @@ -1,4 +0,0 @@ -contractmanifest ----------------- - -.. automodule:: boa3.builtin.interop.contract.contractmanifest diff --git a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract.rst b/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract.rst index 6d6fde8f2..c12f1fb5a 100644 --- a/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract.rst +++ b/docs/source/boa3/builtin/interop/contract/boa3-builtin-interop-contract.rst @@ -2,12 +2,3 @@ contract ======== .. automodule:: boa3.builtin.interop.contract - -Subpackages ------------ - -.. toctree:: - - boa3-builtin-interop-contract-callflags - boa3-builtin-interop-contract-contract - boa3-builtin-interop-contract-contractmanifest diff --git a/docs/source/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.rst b/docs/source/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.rst new file mode 100644 index 000000000..b02afb78a --- /dev/null +++ b/docs/source/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.rst @@ -0,0 +1,4 @@ +oracle +====== + +.. automodule:: boa3.builtin.interop.oracle diff --git a/docs/source/boa3/builtin/interop/policy/boa3-builtin-interop-policy.rst b/docs/source/boa3/builtin/interop/policy/boa3-builtin-interop-policy.rst new file mode 100644 index 000000000..fcc65831b --- /dev/null +++ b/docs/source/boa3/builtin/interop/policy/boa3-builtin-interop-policy.rst @@ -0,0 +1,4 @@ +policy +====== + +.. automodule:: boa3.builtin.interop.policy diff --git a/docs/source/boa3/builtin/interop/role/boa3-builtin-interop-role.rst b/docs/source/boa3/builtin/interop/role/boa3-builtin-interop-role.rst new file mode 100644 index 000000000..b38527ed0 --- /dev/null +++ b/docs/source/boa3/builtin/interop/role/boa3-builtin-interop-role.rst @@ -0,0 +1,4 @@ +role +==== + +.. automodule:: boa3.builtin.interop.role diff --git a/docs/source/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.rst b/docs/source/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.rst index 663583f59..024013de4 100644 --- a/docs/source/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.rst +++ b/docs/source/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.rst @@ -2,13 +2,3 @@ runtime ======= .. automodule:: boa3.builtin.interop.runtime - -Notification ------------- - -.. automodule:: boa3.builtin.interop.runtime.notification - -Trigger -------- - -.. automodule:: boa3.builtin.interop.runtime.triggertype diff --git a/docs/source/boa3/builtin/interop/storage/boa3-builtin-interop-storage.rst b/docs/source/boa3/builtin/interop/storage/boa3-builtin-interop-storage.rst index 2ae510bdf..b145b1236 100644 --- a/docs/source/boa3/builtin/interop/storage/boa3-builtin-interop-storage.rst +++ b/docs/source/boa3/builtin/interop/storage/boa3-builtin-interop-storage.rst @@ -2,13 +2,3 @@ storage ======= .. automodule:: boa3.builtin.interop.storage - -StorageContext --------------- - -.. automodule:: boa3.builtin.interop.storage.storagecontext - -StorageMap ----------- - -.. automodule:: boa3.builtin.interop.storage.storagemap diff --git a/docs/source/calling-smart-contracts.md b/docs/source/calling-smart-contracts.md new file mode 100644 index 000000000..7808e23bb --- /dev/null +++ b/docs/source/calling-smart-contracts.md @@ -0,0 +1,125 @@ +# Calling smart contracts +Currently, there are 2 different ways to call another smart contract on the blockchain: using a `call_contract` method +or using an interface, but internally they work both the same way. + +Let us suppose the following contract was deployed on Neo: +```python +from boa3.builtin.compile_time import public + + +@public +def hello_stranger(name: str) -> str: + return "Hello " + name +``` +To call the `hello_stranger` method, first you'll need to know the smart contract's [script hash](https://developers.neo.org/docs/n3/develop/deploy/deploy#the-contract-scripthash), +Let us also assume it is `0x000102030405060708090A0B0C0D0E0F10111213`. + +## with call_contract +Use the `call_contract` method from `boa3.builtin.interop.contract` on your smart contract. You'll need to use the +script hash, followed by the name of the function you want to call, followed by the arguments of said function. You'll +also need to type cast the return, otherwise it will be considered as `Any`. +```python +# calling_with_call_contract.py +from boa3.builtin.compile_time import public +from boa3.builtin.interop.contract import call_contract +from boa3.builtin.type import UInt160 + +@public +def calling_other_contract() -> str: + greetings: str = call_contract(UInt160(b'\x13\x12\x11\x10\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00'), # usually, script hashes that starts with "0x" means that they are using big endian, so when using `bytes` you'll need to revert the order + 'hello_stranger', # it's the function's name + 'John Doe' # the parameter of 'hello_stranger' + ) + return greetings +``` + +> Note: If you are going to call a contract only once, then it's ok to use `call_contract`, however, it might be hard to +keep track of a lot of `call_contract`s on the same file. It's pretty much always better to use an interface when dealing with other +smart contracts. + +## with Interface +Use the `contract` decorator from `boa3.builtin.compile_time` using the script hash and create a class that have the +same methods you want call. +```python +# calling_with_interface.py +from boa3.builtin.compile_time import public, contract + +@public +def calling_other_contract() -> str: + greetings = HelloStrangerContract.hello_stranger('John Doe') + return greetings + + +@contract('0x000102030405060708090A0B0C0D0E0F10111213') +class HelloStrangerContract: + @staticmethod + def hello_stranger(name: str) -> str: + pass + +``` + +### Calling native contracts +Neo3-boa already has interfaces for all the [native contracts](https://docs.neo.org/docs/en-us/reference/scapi/framework/native.html) +that you can import from `boa3.builtin.nativecontract` +```python +# calling_native_contract.py +from boa3.builtin.compile_time import public +from boa3.builtin.nativecontract import NEO + +@public +def calling_other_contract() -> str: + neo_symbol = NEO.symbol() + return neo_symbol +``` + +### Automate with CPM +Instead of manually writing the smart contract interface, you can use [CPM](https://github.com/CityOfZion/cpm/tree/master#readme) +to generate it automatically. After installing Neo3-boa, you can install CPM by typing `install_cpm` on CLI (without the +`neo3-boa` prefix). Then, you'll need to create a [cpm.yaml config file](https://github.com/CityOfZion/cpm/blob/master/docs/config.md) +and put the smart contract information there, and [run cpm](https://github.com/CityOfZion/cpm#example-commands). + +For example, if you use CPM to create a [dice smart contract](https://dora.coz.io/contract/neo3/mainnet/0x4380f2c1de98bb267d3ea821897ec571a04fe3e0) +interface, the following file will be generated: +```python +# cpm_out/python/dice/contract.py +from boa3.builtin.type import UInt160, UInt256, ECPoint +from boa3.builtin.compile_time import contract, display_name +from typing import cast, Any + + +@contract('0x4380f2c1de98bb267d3ea821897ec571a04fe3e0') +class Dice: + + @staticmethod + def rand_between(start: int, end: int) -> int: + pass + + @staticmethod + def map_bytes_onto_range(start: int, end: int, entropy: bytes) -> int: + pass + + @staticmethod + def roll_die(die: str) -> int: + pass + + @staticmethod + def roll_dice_with_entropy(die: str, precision: int, entropy: bytes) -> list: + pass + + @staticmethod + def update(script: bytes, manifest: bytes, data: Any) -> None: + pass +``` + +Then, all you need to do is import this class onto your smart contract. +```python +# calling_with_cpm.py +from boa3.builtin.compile_time import public +from cpm_out.python.dice.contract import Dice + + +@public +def calling_other_contract() -> str: + d6_roll = Dice.rand_between(1, 6) + return "Die result is " + str(d6_roll) +``` diff --git a/docs/source/code-reference.rst b/docs/source/code-reference.rst deleted file mode 100644 index c90581d07..000000000 --- a/docs/source/code-reference.rst +++ /dev/null @@ -1,311 +0,0 @@ -4. Code Reference -################# - -4.1 Examples -============ - -For an extensive collection of examples: - -- `Smart Contract Examples `_ -- `Feature Tests `_ - -4.2 Supported & Planned Python Features -======================================= - -Variable Declarations ---------------------- - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - Local variable declarations and assignments - - :: - - def func(): - foo: int = 42 - bar = foo - * - ✅ - - Global variable declarations and assignments - - :: - - foo: int = 42 - bar = foo - * - ✅ - - Global keyword - - :: - - foo: int = 42 - bar = foo - - - def func(): - global foo - foo = 1 - -Operations ----------- - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - Arithmetic operations - - :: - - +, -, *, //, %, ** - * - 🔜 - - Arithmetic operations - - :: - - / - * - ✅ - - Arithmetic augmented assignment operators - - :: - - +=, -=, *=, //=, %=, **= - * - 🔜 - - Arithmetic augmented assignment operators - - :: - - /= - * - ✅ - - Relational operations - - :: - - ==, !=, <, <=, >, >=, - is None, is not None, - is, is not - * - ✅ - - Bitwise operations - - :: - - &, |, ~, ^, <<, >> - * - ✅ - - Bitwise augmented assignment operators - - :: - - &=, |=, ~=, ^=, <<=, >>= - * - ✅ - - Boolean logic operations - - :: - - and, or, not - -Types ------ - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - Tuple type - - :: - - a = ('1', '2', '3') - * - ✅ - - List type - - :: - - a = ['1', '2', '3'] - a.pop() - a.remove(1) - a.insert('example', 2) - * - ✅ - - Dict type - - :: - - a = {1:'1', 2:'2', 3:'3'} - * - 🔜 - - Set type - - :: - - a = {'1', '2', '3'} - * - ✅ - - Bytes type - - :: - - a = b'\x01\x02\x03\x04' - * - ✅ - - Bytearray type - - :: - - a = bytearray(b'\x01\x02\x03\x04') - * - ✅ - - Optional type - - :: - - a: Optional[int] = 5 - a = 142 - a = None - * - ✅ - - Union type - - :: - - a: Union[int, str] = 5 - a = 142 - a = 'example' - -Control Flow Statements ------------------------ - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - While statement - - :: - - foo = 0 - while condition: - foo = foo + 2 - * - ✅ - - If, elif, else statements - - :: - - if condition1: - foo = 0 - elif condition2: - foo = 1 - else: - bar = 2 - * - ✅ - - For statement - - :: - - for x in (1, 2, 3): - ... - * - ✅ - - Try except - - :: - - try: - a = foo(b) - except Exception as e: - a = foo(b) - * - ✅ - - Try except with finally - - :: - - try: - a = foo(b) - except Exception as e: - a = zubs(b) - finally: - b = zubs(a) - -Functions ---------- - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - Function call - - :: - - def Main(num: int): - a = foo(num) - ... - - def foo(num: int) -> int: - ... - * - ✅ - - Built in functions - - :: - - a = len('hello') - b = range(1, 5, 2) - c = isinstance(5, str) - print(42) - d = abs(-5) - e = max(7, 12) - f = max(7, 0, 12, 8) - g = min(1, 6) - h = min(1, 6, 2) - i = sum(list_of_num, 0) - j = reversed([1, 2, 3, 4]) - k = pow(2, 2) - -Other Features --------------- - -.. list-table:: - :widths: 3 47 47 - :header-rows: 1 - :align: center - - * - Status - - Feature - - Sample - * - ✅ - - Multiple expressions in the same line - - :: - - i = i + h; a = 1; b = 3 + a; count = 0 - * - ✅ - - Chained assignment - - :: - - x = y = foo() - * - ✅ - - Sequence slicing - - :: - - x = 'example'[2:4] - x = [1, 2, 3][:2] - x = 'example'[4:] - x = (1, 2, 3)[:] - x = 'example'[-4:-2] - x = 'example'[:-4] - x = 'example'[2:4:2] - x = 'example'[::2] - * - ✅ - - Assert - - :: - - assert x % 2 == 0 - assert x % 3 != 2, 'error message' - * - ✅ - - Continue, break - - - * - ✅ - - Pass - - - * - ✅ - - Import - - - - Support to ``boa3.builtin`` packages - - Support to user created modules. - * - ✅ - - Class - - :: - - class Foo: - def __init__(self, bar: Any): - pass diff --git a/docs/source/conceptual-overview.rst b/docs/source/conceptual-overview.rst deleted file mode 100644 index 8d21088ba..000000000 --- a/docs/source/conceptual-overview.rst +++ /dev/null @@ -1,52 +0,0 @@ -3. Conceptual Overview -###################### - -This project is part of the **Neo Python Framework**, aimed to allow the full development of dApps using Python alone. - -**Neo3-Boa** is a tool for creating **Neo Smart Contracts using Python**. It compiles ``.py`` files to ``.nef`` and ``.manifest.json`` formats for usage in the **Neo Virtual Machine** which is used to execute contracts on the **Neo Blockchain**. - -3.1 Project Structure -===================== - -Main Execution Flow -------------------- - -The diagram bellow shows the basic building blocks of the Neo3-Boa project. - -.. image:: diagram.png - :width: 100% - :align: center - :alt: alternate text - -3.2 Product Strategy -==================== - -.. warning:: - - **Review this Section:** Also, a link to a dedicated Neo Python Framework page would seem natural. - -Pure Python ------------ - -We want Python developers to feel comfortable when trying neo3-boa for the first time. It should look and behave like regular Python. For this reason we decided to avoid adding new keywords, but use decorators and helper functions instead. - -Neo Python Framework --------------------- - -In the real world, simply coding a smart contract is not enough. Developers need to debug, deploy and invoke it. Therefore, it’s important for this tool to be part of a bigger Python framework. To help the developers and avoid a bad user experience, we need to use logs and inform errors with details. - -Testing against Neo VM ----------------------- - -We need to ensure that the code works as expected, and the only way to do that is to run our tests against the official Neo 3 VM. Neo organization already has a Neo Test Runner available to C# dApp developers. A NeoTestRunner class was implemented in this project to facilitate testing compiled smart-contracts with Python. - -Maintenance ------------ - -Create a product that is easy to maintain and upgrade. Use Unit tests, typed and documented code to ensure its maintainability. - -3.3 License -=========== -Open-source `Apache 2.0`_. - -.. _Apache 2.0: https://github.com/CityOfZion/neo3-boa/blob/master/LICENSE.md \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 74c81252e..2eda516a6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,7 +36,9 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', - 'sphinx.ext.githubpages'] + 'sphinx.ext.githubpages', + 'myst_parser' + ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -69,7 +71,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/docs/source/getting-started.md b/docs/source/getting-started.md new file mode 100644 index 000000000..4fc30edf1 --- /dev/null +++ b/docs/source/getting-started.md @@ -0,0 +1,110 @@ +# Getting Started + +Check out our [GitHub README page](https://github.com/CityOfZion/neo3-boa/tree/master#quickstart) to see how you can +install Neo3-boa. + +## Writing a smart contract +It's pretty easy to write a Python3 script with Neo3-boa, since it is compatible with a lot of Python features. However, +there are some key differences that you should be aware of, here's the 4 most prominent ones: +- there is no floating point arithmetic, only the `int` type is implemented; +- you need to specify a function's return type and parameter types; +- if you want to call other smart contracts, you can only call public functions; +- to interact with the Neo blockchain, you need to use a function, variable, or class inside the `boa3.builtin` package. + +### Overview of Neo3-boa features + +| Packages | Contains: | Important features | +|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [boa3.builtin](https://dojo.coz.io/neo3/boa/boa3/builtin/boa3-builtins.html ) | all other packages, it also contains an env variable that lets you change the value of a variable when compiling the smart contract. | [env]() | +| [boa3.builtin.compile_time](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html) | methods and classes that are need when you are compiling your smart contract, as opposed to when it's being executed on the blockchain. | [public](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html#boa3.builtin.compile_time.public), [metadata](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html#boa3.builtin.compile_time.metadata), [contract](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html#boa3.builtin.compile_time.contract), [CreateNewEvent](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html#boa3.builtin.compile_time.CreateNewEvent), [NeoMetadata](https://dojo.coz.io/neo3/boa/boa3/builtin/compile-time/boa3-builtin-compile-time.html#boa3.builtin.compile_time.NeoMetadata) | +| [boa3.builtin.contract](https://dojo.coz.io/neo3/boa/boa3/builtin/contract/boa3-builtin-contract.html) | events and methods that might help when writing something specific about Neo blockchain | [abort](https://dojo.coz.io/neo3/boa/boa3/builtin/contract/boa3-builtin-contract.html#boa3.builtin.contract.abort), [Nep17TransferEvent](https://dojo.coz.io/neo3/boa/boa3/builtin/contract/boa3-builtin-contract.html#boa3.builtin.contract.Nep17TransferEvent), [Nep11TransferEvent](https://dojo.coz.io/neo3/boa/boa3/builtin/contract/boa3-builtin-contract.html#boa3.builtin.contract.Nep11TransferEvent) | +| [boa3.builtin.interop](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/boa3-builtin-interop.html) | other packages that have a lot of helpful interoperable services. Has some overlap with the native contracts. | [storage](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html), [runtime](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html), [contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/contract/boa3-builtin-interop-contract.html), [blockchain](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.html) | +| [boa3.builtin.interop.blockchain](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.html) | features to get information on the Neo blockchain. | [current_hash](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.html#boa3.builtin.interop.blockchain.current_hash), [get_contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.html#boa3.builtin.interop.blockchain.get_contract), [Transaction](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/blockchain/boa3-builtin-interop-blockchain.html#boa3.builtin.interop.blockchain.transaction.Transaction) | +| [boa3.builtin.interop.contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/contract/boa3-builtin-interop-contract.html) | features related to smart contracts. | [call_contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/contract/boa3-builtin-interop-contract.html#boa3.builtin.interop.contract.call_contract), [update_contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/contract/boa3-builtin-interop-contract.html#boa3.builtin.interop.contract.update_contract), [Contract](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/contract/boa3-builtin-interop-contract.html#boa3.builtin.interop.contract.Contract) | +| [boa3.builtin.interop.crypto](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/crypto/boa3-builtin-interop-crypto.html) | features related to cryptography. | [sha256](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/crypto/boa3-builtin-interop-crypto.html#boa3.builtin.interop.crypto.sha256), [hash160](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/crypto/boa3-builtin-interop-crypto.html#boa3.builtin.interop.crypto.hash160), [hash256](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/crypto/boa3-builtin-interop-crypto.html#boa3.builtin.interop.crypto.hash256), [check_sig](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/crypto/boa3-builtin-interop-crypto.html#boa3.builtin.interop.crypto.check_sig) | +| [boa3.builtin.interop.iterator](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/iterator/boa3-builtin-interop-iterator.html) | the iterator class. | [Iterator](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/iterator/boa3-builtin-interop-iterator.html#boa3.builtin.interop.iterator.Iterator) | +| [boa3.builtin.interop.json](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/json/boa3-builtin-interop-json.html) | methods to serialize and deserialize JSON. | [json_serialize](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/json/boa3-builtin-interop-json.html#boa3.builtin.interop.json.json_serialize), [json_deserialize](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/json/boa3-builtin-interop-json.html#boa3.builtin.interop.json.json_deserialize) | +| [boa3.builtin.interop.oracle](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.html) | features related with Neo Oracle, it is used to get information from outside the blockchain. | [Oracle](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/oracle/boa3-builtin-interop-oracle.html#boa3.builtin.interop.oracle.Oracle) | +| [boa3.builtin.interop.policy](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/policy/boa3-builtin-interop-policy.html) | features related with the policy that affects all of the Neo blockchain. | [get_exec_fee_factor](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/policy/boa3-builtin-interop-policy.html#boa3.builtin.interop.policy.get_exec_fee_factor), [get_storage_price](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/policy/boa3-builtin-interop-policy.html#boa3.builtin.interop.policy.get_storage_price) | +| [boa3.builtin.interop.role](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/role/boa3-builtin-interop-role.html) | methods to get information about the nodes on the blockchain. | [get_designated_by_role](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/role/boa3-builtin-interop-role.html#boa3.builtin.interop.role.get_designated_by_role) | +| [boa3.builtin.interop.runtime](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html) | features to get information that can only be acquired when running the smart contract. | [check_witness](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html#boa3.builtin.interop.runtime.check_witness), [calling_script_hash](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html#boa3.builtin.interop.runtime.calling_script_hash), [executing_script_hash](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html#boa3.builtin.interop.runtime.executing_script_hash), [script_container](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html#boa3.builtin.interop.runtime.script_container), [Notification](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/runtime/boa3-builtin-interop-runtime.html#boa3.builtin.interop.runtime.script_container) | +| [boa3.builtin.interop.stdlib](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/stdlib/boa3-builtin-interop-stdlib.html) | methods that convert one data to another or methods that can check and compare memory. | [serialize](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/stdlib/boa3-builtin-interop-stdlib.html#boa3.builtin.interop.stdlib.serialize), [deserialize](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/stdlib/boa3-builtin-interop-stdlib.html#boa3.builtin.interop.stdlib.deserialize) | +| [boa3.builtin.interop.storage](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html) | features to store, get, or change values inside the blockchain. | [get](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html#boa3.builtin.interop.storage.get), [put](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html#boa3.builtin.interop.storage.put), [find](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html#boa3.builtin.interop.storage.find), [FindOptions](https://dojo.coz.io/neo3/boa/boa3/builtin/interop/storage/boa3-builtin-interop-storage.html#boa3.builtin.interop.storage.FindOptions) | +| [boa3.builtin.nativecontract](https://dojo.coz.io/neo3/boa/boa3/builtin/nativecontract/boa3-builtin-nativecontract.html) | classes that interface Neo's native contracts. | [ContractManagement](https://dojo.coz.io/neo3/boa/boa3/builtin/nativecontract/boa3-builtin-nativecontract-contractmanagement.html), [GAS](https://dojo.coz.io/neo3/boa/boa3/builtin/nativecontract/boa3-builtin-nativecontract-gas.html), [NEO](https://dojo.coz.io/neo3/boa/boa3/builtin/nativecontract/boa3-builtin-nativecontract-neo.html), [StdLib](https://dojo.coz.io/neo3/boa/boa3/builtin/nativecontract/boa3-builtin-nativecontract-stdlib.html) | +| [boa3.builtin.type](https://dojo.coz.io/neo3/boa/boa3/builtin/type/boa3-builtin-type.html) | Neo types. | [UInt160](https://dojo.coz.io/neo3/boa/boa3/builtin/type/boa3-builtin-type.html#boa3.builtin.type.UInt160), [UInt256](https://dojo.coz.io/neo3/boa/boa3/builtin/type/boa3-builtin-type.html#boa3.builtin.type.UInt256), [Event](https://dojo.coz.io/neo3/boa/boa3/builtin/type/boa3-builtin-type.html#boa3.builtin.type.Event), [ECPoint](https://dojo.coz.io/neo3/boa/boa3/builtin/type/boa3-builtin-type.html#boa3.builtin.type.ECPoint) | +| [boa3.builtin.vm](https://dojo.coz.io/neo3/boa/boa3/builtin/vm/boa3-builtin-vm.html) | Opcodes used internally by the NeoVM, you'll probably won't need to use this package. | [Opcode](https://dojo.coz.io/neo3/boa/boa3/builtin/vm/boa3-builtin-vm.html#boa3.builtin.vm.Opcode) | +| [boa3.builtin.math](https://dojo.coz.io/neo3/boa/boa3/builtin/boa3-builtin-math.html) | a small sample of functions similar to Python's math. | [sqrt](https://dojo.coz.io/neo3/boa/boa3/builtin/boa3-builtin-math.html#boa3.builtin.math.sqrt), [floor](https://dojo.coz.io/neo3/boa/boa3/builtin/boa3-builtin-math.html#boa3.builtin.math.floor), [ceil](https://dojo.coz.io/neo3/boa/boa3/builtin/boa3-builtin-math.html#boa3.builtin.math.ceil) | + + +### Hello World +Let's write a quick Hello World script that has a method that will save `"Hello World"` on the blockchain +and another method that will return the string. + +Those 2 functions will need to be callable and will also need to change the values inside the storage, +so let's import both the `public` decorator and the `storage` package. + +```python +# hello_world.py +from boa3.builtin.compile_time import public +from boa3.builtin.interop import storage + + +@public # the public decorator will make this method callable +def save_hello_world(): # an empty return type indicates that the return is None + storage.put("first script", "Hello World") # the put method will store the "Hello World" value with the "first script" key + + +@public # the public decorator will make this method callable too +def get_hello_world() -> str: # this method will return a string, so it needs to specify it + return storage.get("first script") # the get method will return the value associated with "first script" key +``` + +Alternatively, instead of creating a `save_hello_world` function and having to call it yourself, you could instead create +a public `_deploy` function and it will automatically be called when you deploy your smart contract into the blockchain. + +```python +# hello_world.py +from typing import Any + +from boa3.builtin.compile_time import public +from boa3.builtin.interop import storage + + +@public +def _deploy(data: Any, update: bool): # the _deploy function needs to have this signature + storage.put("first script", "Hello World") # "Hello World" will be stored when this smart contract is deployed + + +@public +def get_hello_world() -> str: + return storage.get("first script") +``` + +## Compiling your Smart Contract + +### Using CLI + +```shell +$ neo3-boa path/to/your/file.py +``` + +> Note: When resolving compilation errors it is recommended to resolve the first reported error and try to compile again. An error can have a cascading effect and throw more errors all caused by the first. + +### Using Python Script + +```python +from boa3.boa3 import Boa3 + +Boa3.compile_and_save('path/to/your/file.py') +``` + +## Reference Examples + +For an extensive collection of examples: +- [Smart contract examples](https://github.com/CityOfZion/neo3-boa/blob/development/boa3_test/examples) +- [Features tests](https://github.com/CityOfZion/neo3-boa/blob/development/boa3_test/test_sc) + + +## What's next +- [Testing and debugging your smart contract](./testing-and-debugging.md) +- [How to call other smart contracts on the blockchain](./calling-smart-contracts.md) diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst deleted file mode 100644 index 25fd3535c..000000000 --- a/docs/source/getting-started.rst +++ /dev/null @@ -1,162 +0,0 @@ -1. Getting Started -################## - -**Neo3-Boa** is a tool for creating **Neo Smart Contracts using Python**. It compiles ``.py`` files to ``.nef`` and ``.manifest.json`` formats for usage in the **Neo Virtual Machine** which is used to execute contracts on the **Neo Blockchain**. - -1.1 Installation -================ - -This version of the compiler requires Python 3.7 or later. - -Set Virtual Environment ------------------------ - -Make a Python 3 virtual environment and activate it: - -On Linux/MacOS: -*************** -:: - - $ python3 -m venv venv - $ source venv/bin/activate - -On Windows: -*********** -:: - - $ python3 -m venv venv - $ venv\Scripts\activate.bat - -Pip Install (Recommended) -------------------------- - -:: - - pip install neo3-boa - -Build From Source (Alternative) -------------------------------- - -If neo3-boa is not available via pip, you can build it from source. - -:: - - git clone https://github.com/CityOfZion/neo3-boa.git - pip install wheel - pip install -e . - -1.2 Creating a New Smart Contract -================================= - -.. warning:: - - **CONTENT MISSING:** Project Scaffold - GitHub `#307 `_ and `#308 `_ - - -1.3 Compiling your Smart Contract -================================= - -Using CLI ---------- -:: - - $ neo3-boa path/to/your/file.py - -.. note:: - When resolving compilation errors it is recommended to resolve the first reported error and try to compile again. An error can have a cascading effect and throw more errors all caused by the first. - -Using Python Script -------------------- - -:: - - from boa3.boa3 import Boa3 - - Boa3.compile_and_save('path/to/your/file.py') - - -1.4 Testing and Debugging -========================= - -Configuring the Debugger ------------------------- - -Neo3-boa is compatible with the Neo Debugger. Debugger launch configuration example: - -:: - - { - //Launch configuration example for Neo3-boa. - //Make sure you compile your smart-contract before you try to debug it. - "version": "0.2.0", - "configurations": [ - { - "name": "example.nef", - "type": "neo-contract", - "request": "launch", - "program": "${workspaceFolder}\\example.nef", - "operation": "main", - "args": [], - "storage": [], - "runtime": { - "witnesses": { - "check-result": true - } - } - } - ] - } - -It's necessary to generate the nef debugger info file to use Neo Debugger. - -Using CLI ---------- -:: - - $ neo3-boa path/to/your/file.py -d|--debug - -Using Python Script -------------------- - -:: - - from boa3.boa3 import Boa3 - - Boa3.compile_and_save('path/to/your/file.py', debug=True) - - -Neo Test Runner ---------------- - -Downloading -*********** - -Install `Neo-Express `_ and `NeoTestRunner `_. - -:: - - $ dotnet tool install Neo.Express - $ dotnet tool install Neo.Test.Runner - - -Testing -******* - -Create a Python Script, import the NeoTestRunner class, and define a function to test your smart contract. In this function you'll need to call the method call_contract(). Its parameters are the path of the compiled smart contract, the smart contract's method, and the arguments if necessary. Then assert the result to see if it's correct. - -Your Python Script should look something like this: - -:: - - from boa3_test.test_drive.testrunner.neo_test_runner import NeoTestRunner - - def test_hello_world_main(): - neoxp_folder = '{path-to-neo-express-directory}' - path = '%s/boa3_test/examples/HelloWorld.nef' % root_folder - runner = NeoTestRunner(neoxp_folder) - - invoke = runner.call_contract(path, 'Main') - runner.execute() - assert invoke.result is None - -To run all tests run the python script at boa3_test/tests/run_unit_tests.py diff --git a/docs/source/index.rst b/docs/source/index.rst index 454c93000..bdb7acdf0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,7 +10,6 @@ Welcome to neo3-boa's documentation! :caption: Contents: getting-started - tutorials - conceptual-overview - code-reference + testing-and-debugging + calling-smart-contracts package-reference \ No newline at end of file diff --git a/docs/source/package-reference.rst b/docs/source/package-reference.rst index 2493168c3..77c09946a 100644 --- a/docs/source/package-reference.rst +++ b/docs/source/package-reference.rst @@ -1,5 +1,5 @@ -5. Package Reference -==================== +Package Reference +================= .. toctree:: :caption: Contents: diff --git a/docs/source/testing-and-debugging.md b/docs/source/testing-and-debugging.md new file mode 100644 index 000000000..68ac07a16 --- /dev/null +++ b/docs/source/testing-and-debugging.md @@ -0,0 +1,99 @@ +# Testing and Debugging + +## Configuring the Debugger +Neo3-boa is compatible with the [Neo Debugger](https://github.com/neo-project/neo-debugger). +Debugger launch configuration example: +``` +{ + //Launch configuration example for Neo3-boa. + //Make sure you compile your smart-contract before you try to debug it. + "version": "0.2.0", + "configurations": [ + { + "name": "example.nef", + "type": "neo-contract", + "request": "launch", + "program": "${workspaceFolder}\\example.nef", + "operation": "main", + "args": [], + "storage": [], + "runtime": { + "witnesses": { + "check-result": true + } + } + } + ] +} +``` + +It's necessary to generate the nef debugger info file to use Neo Debugger. + +### Using CLI + +```shell +$ neo3-boa path/to/your/file.py -d|--debug +``` + +### Using Python Script + +```python +from boa3.boa3 import Boa3 + +Boa3.compile_and_save('path/to/your/file.py', debug=True) +``` + +## Neo Test Runner + +### Downloading + +Install [Neo-Express](https://github.com/neo-project/neo-express#neo-express-and-neo-trace) and [Neo Test Runner](https://github.com/ngdenterprise/neo-test#neo-test-runner). + +```shell +$ dotnet tool install Neo.Express +$ dotnet tool install Neo.Test.Runner +``` + +### Testing + +Create a Python Script, import the NeoTestRunner class, and define a function to test your smart contract. In this +function you'll need to call the method `call_contract()`. Its parameters are the path of the compiled smart contract, +the smart contract's method, and the arguments if necessary. Then assert the result of your invoke to see if it's correct. + +Your Python Script should look something like this: + +```python +from boa3_test.test_drive.testrunner.neo_test_runner import NeoTestRunner + + +def test_hello_world_main(): + neoxp_folder = '{path-to-neo-express-directory}' + project_root_folder = '{path-to-project-root-folder}' + path = f'{project_root_folder}/boa3_test/examples/hello_world.nef' + runner = NeoTestRunner(neoxp_folder) + + invoke = runner.call_contract(path, 'main') + runner.execute() + assert invoke.result is None +``` + +Alternatively you can change the value of `boa3.env.NEO_EXPRESS_INSTANCE_DIRECTORY` to the path of your .neo-express +data file: + +```python +from boa3_test.test_drive.testrunner.neo_test_runner import NeoTestRunner +from boa3.internal import env + +env.NEO_EXPRESS_INSTANCE_DIRECTORY = '{path-to-neo-express-directory}' + + +def test_hello_world_main(): + root_folder = '{path-to-project-root-folder}' + path = f'{root_folder}/boa3_test/examples/hello_world.nef' + runner = NeoTestRunner() # the default path to the Neo-Express is the one on env.NEO_EXPRESS_INSTANCE_DIRECTORY + + invoke = runner.call_contract(path, 'main') + runner.execute() + assert invoke.result is None +``` + diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst deleted file mode 100644 index 6db9e7e3b..000000000 --- a/docs/source/tutorials.rst +++ /dev/null @@ -1,1465 +0,0 @@ -2. Tutorials -############ - -This section presents a few examples of Python code that can be compiled by **Neo3-Boa** into actual Smart Contracts and deployed to the Neo Blockchain. - -The main goal of these tutorials is to introduce Blockchain concepts to the Python developer. In each of them, we will try to highlight basic concepts of Smart Contract logic, pinpointing the ways in which it differs from usual programming logic, and the structural reasons behind those differences. - -All of the examples presented here can be found in the `examples folder of the Neo3-Boa repository `_ - -2.1 Hello World -=============== - -.. warning:: - - **CONTENT MISSING:** Brief Tutorial Description of Hello World - -:: - - from boa3.builtin.compile_time import NeoMetadata, metadata, public - from boa3.builtin.interop import storage - - - @public - def Main(): - storage.put('hello', 'world') - - - @metadata - def manifest() -> NeoMetadata: - meta = NeoMetadata() - - meta.author = "COZ in partnership with Simpli" - meta.email = "contact@coz.io" - meta.description = 'This is a contract example' - return meta - - - -2.2 Neo Token Standard (NEP-17) -=============================== - -.. warning:: - - **CONTENT MISSING:** Brief Tutorial Description of NEP-17 - -:: - - from typing import Any, Union - - from boa3.builtin.compile_time import NeoMetadata, metadata, public - from boa3.builtin.contract import Nep17TransferEvent, abort - from boa3.builtin.interop import runtime, storage - from boa3.builtin.interop.contract import GAS as GAS_SCRIPT, NEO as NEO_SCRIPT, call_contract - from boa3.builtin.nativecontract.contractmanagement import ContractManagement - from boa3.builtin.type import UInt160 - - - # ------------------------------------------- - # METADATA - # ------------------------------------------- - - @metadata - def manifest_metadata() -> NeoMetadata: - """ - Defines this smart contract's metadata information - """ - meta = NeoMetadata() - meta.supported_standards = ['NEP-17'] - meta.add_permission(methods=['onNEP17Payment']) - - meta.author = "Mirella Medeiros, Ricardo Prado and Lucas Uezu. COZ in partnership with Simpli" - meta.description = "NEP-17 Example" - meta.email = "contact@coz.io" - return meta - - - # ------------------------------------------- - # TOKEN SETTINGS - # ------------------------------------------- - - - # Script hash of the contract owner - OWNER = UInt160() - SUPPLY_KEY = 'totalSupply' - - # Symbol of the Token - TOKEN_SYMBOL = 'NEP17' - - # Number of decimal places - TOKEN_DECIMALS = 8 - - # Total Supply of tokens in the system - TOKEN_TOTAL_SUPPLY = 10_000_000 * 10 ** TOKEN_DECIMALS # 10m total supply * 10^8 (decimals) - - # Value of this NEP-17 token corresponds to NEO - AMOUNT_PER_NEO = 10 - - # Value of this NEP-17 token compared to GAS - AMOUNT_PER_GAS = 2 - - # ------------------------------------------- - # Events - # ------------------------------------------- - - - on_transfer = Nep17TransferEvent - - - # ------------------------------------------- - # Methods - # ------------------------------------------- - - - @public(safe=True) - def symbol() -> str: - """ - Gets the symbols of the token. - - This string must be valid ASCII, must not contain whitespace or control characters, should be limited to uppercase - Latin alphabet (i.e. the 26 letters used in English) and should be short (3-8 characters is recommended). - This method must always return the same value every time it is invoked. - - :return: a short string representing symbol of the token managed in this contract. - """ - return TOKEN_SYMBOL - - - @public(safe=True) - def decimals() -> int: - """ - Gets the amount of decimals used by the token. - - E.g. 8, means to divide the token amount by 100,000,000 (10 ^ 8) to get its user representation. - This method must always return the same value every time it is invoked. - - :return: the number of decimals used by the token. - """ - return TOKEN_DECIMALS - - - @public(name='totalSupply', safe=True) - def total_supply() -> int: - """ - Gets the total token supply deployed in the system. - - This number must not be in its user representation. E.g. if the total supply is 10,000,000 tokens, this method - must return 10,000,000 * 10 ^ decimals. - - :return: the total token supply deployed in the system. - """ - return storage.get(SUPPLY_KEY).to_int() - - - @public(name='balanceOf', safe=True) - def balance_of(account: UInt160) -> int: - """ - Get the current balance of an address - - The parameter account must be a 20-byte address represented by a UInt160. - - :param account: the account address to retrieve the balance for - :type account: UInt160 - """ - assert len(account) == 20 - return storage.get(account).to_int() - - - @public - def transfer(from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: - """ - Transfers an amount of NEP17 tokens from one account to another - - If the method succeeds, it must fire the `Transfer` event and must return true, even if the amount is 0, - or from and to are the same address. - - :param from_address: the address to transfer from - :type from_address: UInt160 - :param to_address: the address to transfer to - :type to_address: UInt160 - :param amount: the amount of NEP17 tokens to transfer - :type amount: int - :param data: whatever data is pertinent to the onPayment method - :type data: Any - - :return: whether the transfer was successful - :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` is less than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(from_address) == 20 and len(to_address) == 20 - # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. - assert amount >= 0 - - # The function MUST return false if the from account balance does not have enough tokens to spend. - from_balance = storage.get(from_address).to_int() - if from_balance < amount: - return False - - # The function should check whether the from address equals the caller contract hash. - # If so, the transfer should be processed; - # If not, the function should use the check_witness to verify the transfer. - if from_address != runtime.calling_script_hash: - if not runtime.check_witness(from_address): - return False - - # skip balance changes if transferring to yourself or transferring 0 cryptocurrency - if from_address != to_address and amount != 0: - if from_balance == amount: - storage.delete(from_address) - else: - storage.put(from_address, from_balance - amount) - - to_balance = storage.get(to_address).to_int() - storage.put(to_address, to_balance + amount) - - # if the method succeeds, it must fire the transfer event - on_transfer(from_address, to_address, amount) - # if the to_address is a smart contract, it must call the contracts onPayment - post_transfer(from_address, to_address, amount, data) - # and then it must return true - return True - - - def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any): - """ - Checks if the one receiving NEP17 tokens is a smart contract and if it's one the onPayment method will be called - - :param from_address: the address of the sender - :type from_address: UInt160 - :param to_address: the address of the receiver - :type to_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - """ - if to_address is not None: - contract = ContractManagement.get_contract(to_address) - if contract is not None: - call_contract(to_address, 'onNEP17Payment', [from_address, amount, data]) - - - def mint(account: UInt160, amount: int): - """ - Mints new tokens. This is not a NEP-17 standard method, it's only being use to complement the onPayment method - - :param account: the address of the account that is sending cryptocurrency to this contract - :type account: UInt160 - :param amount: the amount of gas to be refunded - :type amount: int - :raise AssertionError: raised if amount is less than than 0 - """ - assert amount >= 0 - if amount != 0: - current_total_supply = total_supply() - account_balance = balance_of(account) - - storage.put(SUPPLY_KEY, current_total_supply + amount) - storage.put(account, account_balance + amount) - - on_transfer(None, account, amount) - post_transfer(None, account, amount, None) - - - @public - def verify() -> bool: - """ - When this contract address is included in the transaction signature, - this method will be triggered as a VerificationTrigger to verify that the signature is correct. - For example, this method needs to be called when withdrawing token from the contract. - - :return: whether the transaction signature is correct - """ - return runtime.check_witness(OWNER) - - - @public - def _deploy(data: Any, update: bool): - """ - Initializes the storage when the smart contract is deployed. - - :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. - """ - if not update: - storage.put(SUPPLY_KEY, TOKEN_TOTAL_SUPPLY) - storage.put(OWNER, TOKEN_TOTAL_SUPPLY) - - on_transfer(None, OWNER, TOKEN_TOTAL_SUPPLY) - - - @public - def onNEP17Payment(from_address: UInt160, amount: int, data: Any): - """ - NEP-17 affirms :"if the receiver is a deployed contract, the function MUST call onPayment method on receiver - contract with the data parameter from transfer AFTER firing the Transfer event. If the receiver doesn't want to - receive this transfer it MUST call ABORT." Therefore, since this is a smart contract, onPayment must exists. - - There is no guideline as to how it should verify the transaction and it's up to the user to make this verification. - - For instance, this onPayment method checks if this smart contract is receiving NEO or GAS so that it can mint a - NEP17 token. If it's not receiving a native token, than it will abort. - - :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract - :type from_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent to the this smart contract - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - """ - # Use calling_script_hash to identify if the incoming token is NEO or GAS - if runtime.calling_script_hash == NEO_SCRIPT: - corresponding_amount = amount * AMOUNT_PER_NEO - mint(from_address, corresponding_amount) - elif runtime.calling_script_hash == GAS_SCRIPT: - corresponding_amount = amount * AMOUNT_PER_GAS - mint(from_address, corresponding_amount) - else: - abort() - -2.3 Hashed Timelock Contract (HTLC) -=================================== - -.. warning:: - - **CONTENT MISSING:** Brief Tutorial Description of HTLC - -:: - - from typing import Any - - from boa3.builtin.compile_time import NeoMetadata, metadata, public - from boa3.builtin.contract import abort - from boa3.builtin.interop import runtime, storage - from boa3.builtin.interop.contract import GAS as GAS_SCRIPT, call_contract - from boa3.builtin.interop.crypto import hash160 - from boa3.builtin.type import UInt160 - - - # ------------------------------------------- - # METADATA - # ------------------------------------------- - - - @metadata - def manifest_metadata() -> NeoMetadata: - """ - Defines this smart contract's metadata information - """ - meta = NeoMetadata() - return meta - - - # ------------------------------------------- - # VARIABLES SETTINGS - # ------------------------------------------- - - - OWNER = UInt160() - PERSON_A: bytes = b'person a' - PERSON_B: bytes = b'person b' - ADDRESS_PREFIX: bytes = b'address' - AMOUNT_PREFIX: bytes = b'amount' - TOKEN_PREFIX: bytes = b'token' - FUNDED_PREFIX: bytes = b'funded' - - # Number of seconds that need to pass before refunding the contract - LOCK_TIME = 15 * 1 - - NOT_INITIALIZED: bytes = b'not initialized' - START_TIME: bytes = b'start time' - SECRET_HASH: bytes = b'secret hash' - - - # ------------------------------------------- - # Methods - # ------------------------------------------- - - - @public - def verify() -> bool: - """ - When this contract address is included in the transaction signature, - this method will be triggered as a VerificationTrigger to verify that the signature is correct. - For example, this method needs to be called when withdrawing token from the contract. - - :return: whether the transaction signature is correct - """ - return runtime.check_witness(OWNER) - - - @public - def _deploy(data: Any, update: bool): - """ - Initializes OWNER and change values of NOT_INITIALIZED and DEPLOYED when the smart contract is deployed. - - :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. - """ - if not update: - storage.put(OWNER, OWNER) - storage.put(NOT_INITIALIZED, True) - - - @public - def atomic_swap(person_a_address: UInt160, person_a_token: bytes, person_a_amount: int, person_b_address: UInt160, - person_b_token: bytes, person_b_amount: int, secret_hash: bytes) -> bool: - """ - Initializes the storage when the atomic swap starts. - - :param person_a_address: address of person_a - :type person_a_address: UInt160 - :param person_a_token: person_b's desired token - :type person_a_token: bytes - :param person_a_amount: person_b's desired amount of tokens - :type person_a_amount: int - :param person_b_address: address of person_b - :type person_b_address: bytes - :param person_b_token: person_a's desired token - :type person_b_token: bytes - :param person_b_amount: person_a's desired amount of tokens - :type person_b_amount: int - :param secret_hash: the secret hash created by the contract deployer - :type secret_hash: bytes - - :return: whether the deploy was successful or not - :rtype: bool - - :raise AssertionError: raised if `person_a_address` or `person_b_address` length is not 20 or if `amount` is not - greater than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(person_a_address) == 20 and len(person_b_address) == 20 - # the parameter amount must be greater than 0. If not, this method should throw an exception. - assert person_a_amount > 0 and person_b_amount > 0 - - if storage.get(NOT_INITIALIZED).to_bool() and verify(): - storage.put(ADDRESS_PREFIX + PERSON_A, person_a_address) - storage.put(TOKEN_PREFIX + PERSON_A, person_a_token) - storage.put(AMOUNT_PREFIX + PERSON_A, person_a_amount) - storage.put(ADDRESS_PREFIX + PERSON_B, person_b_address) - storage.put(TOKEN_PREFIX + PERSON_B, person_b_token) - storage.put(AMOUNT_PREFIX + PERSON_B, person_b_amount) - storage.put(SECRET_HASH, secret_hash) - storage.put(NOT_INITIALIZED, False) - storage.put(START_TIME, runtime.time) - return True - return False - - - @public - def onNEP17Payment(from_address: UInt160, amount: int, data: Any): - """ - Since this is a deployed contract, transfer will be calling this onPayment method with the data parameter from - transfer. If someone is doing a not required transfer, then ABORT will be called. - - :param from_address: the address of the one who is trying to transfer cryptocurrency to this smart contract - :type from_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent to this smart contract - :type amount: int - :param data: any pertinent data that may validate the transaction - :type data: Any - - :raise AssertionError: raised if `from_address` length is not 20 - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - aux_var = from_address is not None # TODO: using identity operators or isinstance as a condition of an if is bugged - if aux_var: - assert len(from_address) == 20 - - # this validation will verify if Neo is trying to mint GAS to this smart contract - aux_var = from_address is None # TODO: using identity operators or isinstance as a condition of an if is bugged - if aux_var and runtime.calling_script_hash == GAS_SCRIPT: - return - - if not storage.get(NOT_INITIALIZED).to_bool(): - # Used to check if the one who's transferring to this contract is the PERSON_A - address = storage.get(ADDRESS_PREFIX + PERSON_A) - # Used to check if PERSON_A already transfer to this smart contract - funded_crypto = storage.get(FUNDED_PREFIX + PERSON_A).to_int() - # Used to check if PERSON_A is transferring the correct amount - amount_crypto = storage.get(AMOUNT_PREFIX + PERSON_A).to_int() - # Used to check if PERSON_A is transferring the correct token - token_crypto = storage.get(TOKEN_PREFIX + PERSON_A) - if (from_address == address and - funded_crypto == 0 and - amount == amount_crypto and - runtime.calling_script_hash == token_crypto): - storage.put(FUNDED_PREFIX + PERSON_A, amount) - return - else: - # Used to check if the one who's transferring to this contract is the OTHER_PERSON - address = storage.get(ADDRESS_PREFIX + PERSON_B) - # Used to check if PERSON_B already transfer to this smart contract - funded_crypto = storage.get(FUNDED_PREFIX + PERSON_B).to_int() - # Used to check if PERSON_B is transferring the correct amount - amount_crypto = storage.get(AMOUNT_PREFIX + PERSON_B).to_int() - # Used to check if PERSON_B is transferring the correct token - token_crypto = storage.get(TOKEN_PREFIX + PERSON_B) - if (from_address == address and - funded_crypto == 0 and - amount == amount_crypto and - runtime.calling_script_hash == token_crypto): - storage.put(FUNDED_PREFIX + PERSON_B, amount) - return - abort() - - - @public - def withdraw(secret: str) -> bool: - """ - Deposits the contract's cryptocurrency into the person_a and person_b addresses as long as they both transferred - to this contract and there is some time remaining - - :param secret: the private key that unlocks the transaction - :type secret: str - - :return: whether the transfers were successful - :rtype: bool - """ - # Checking if PERSON_A and PERSON_B transferred to this smart contract - funded_person_a = storage.get(FUNDED_PREFIX + PERSON_A).to_int() - funded_person_b = storage.get(FUNDED_PREFIX + PERSON_B).to_int() - if verify() and not refund() and hash160(secret) == storage.get(SECRET_HASH) and funded_person_a != 0 and funded_person_b != 0: - storage.put(FUNDED_PREFIX + PERSON_A, 0) - storage.put(FUNDED_PREFIX + PERSON_B, 0) - storage.put(NOT_INITIALIZED, True) - storage.put(START_TIME, 0) - call_contract(UInt160(storage.get(TOKEN_PREFIX + PERSON_B)), 'transfer', - [runtime.executing_script_hash, storage.get(ADDRESS_PREFIX + PERSON_A), storage.get(AMOUNT_PREFIX + PERSON_B), None]) - call_contract(UInt160(storage.get(TOKEN_PREFIX + PERSON_A)), 'transfer', - [runtime.executing_script_hash, storage.get(ADDRESS_PREFIX + PERSON_B), storage.get(AMOUNT_PREFIX + PERSON_A), None]) - return True - - return False - - - @public - def refund() -> bool: - """ - If the atomic swap didn't occur in time, refunds the cryptocurrency that was deposited in this smart contract - - :return: whether enough time has passed and the cryptocurrencies were refunded - :rtype: bool - """ - if runtime.time > storage.get(START_TIME).to_int() + LOCK_TIME: - # Checking if PERSON_A transferred to this smart contract - funded_crypto = storage.get(FUNDED_PREFIX + PERSON_A).to_int() - if funded_crypto != 0: - call_contract(UInt160(storage.get(TOKEN_PREFIX + PERSON_A)), 'transfer', - [runtime.executing_script_hash, UInt160(storage.get(ADDRESS_PREFIX + PERSON_A)), storage.get(AMOUNT_PREFIX + PERSON_A).to_int(), None]) - - # Checking if PERSON_B transferred to this smart contract - funded_crypto = storage.get(FUNDED_PREFIX + PERSON_B).to_int() - if funded_crypto != 0: - call_contract(UInt160(storage.get(TOKEN_PREFIX + PERSON_B)), 'transfer', - [runtime.executing_script_hash, storage.get(ADDRESS_PREFIX + PERSON_B), storage.get(AMOUNT_PREFIX + PERSON_B).to_int(), None]) - storage.put(FUNDED_PREFIX + PERSON_A, 0) - storage.put(FUNDED_PREFIX + PERSON_B, 0) - storage.put(NOT_INITIALIZED, True) - storage.put(START_TIME, 0) - return True - return False - - -2.4 Initial Coin Offering (ICO) -=============================== - -.. warning:: - - **CONTENT MISSING:** Brief Tutorial Description of ICO - -:: - - from typing import Any, List, Union - - from boa3.builtin.compile_time import NeoMetadata, metadata, public - from boa3.builtin.contract import Nep17TransferEvent - from boa3.builtin.interop import runtime, storage - from boa3.builtin.interop.contract import call_contract - from boa3.builtin.nativecontract.contractmanagement import ContractManagement - from boa3.builtin.nativecontract.gas import GAS as GAS_TOKEN - from boa3.builtin.nativecontract.neo import NEO as NEO_TOKEN - from boa3.builtin.type import UInt160 - - - # ------------------------------------------- - # METADATA - # ------------------------------------------- - - - @metadata - def manifest_metadata() -> NeoMetadata: - """ - Defines this smart contract's metadata information - """ - meta = NeoMetadata() - meta.supported_standards = ['NEP-17'] - meta.add_permission(methods=['onNEP17Payment']) - - meta.author = "Mirella Medeiros, Ricardo Prado and Lucas Uezu. COZ in partnership with Simpli" - meta.description = "ICO Example" - meta.email = "contact@coz.io" - return meta - - - # ------------------------------------------- - # Storage Key Prefixes - # ------------------------------------------- - - - KYC_WHITELIST_PREFIX = b'KYCWhitelistApproved' - TOKEN_TOTAL_SUPPLY_PREFIX = b'TokenTotalSupply' - TRANSFER_ALLOWANCE_PREFIX = b'TransferAllowancePrefix_' - - - # ------------------------------------------- - # TOKEN SETTINGS - # ------------------------------------------- - - - # Script hash of the contract owner - TOKEN_OWNER = UInt160() - - # Symbol of the Token - TOKEN_SYMBOL = 'ICO' - - # Number of decimal places - TOKEN_DECIMALS = 8 - - # Initial Supply of tokens in the system - TOKEN_INITIAL_SUPPLY = 10_000_000 * 10 ** TOKEN_DECIMALS # 10m total supply * 10^8 (decimals) - - # ------------------------------------------- - # Events - # ------------------------------------------- - - - on_transfer = Nep17TransferEvent - - - # ------------------------------------------- - # Methods - # ------------------------------------------- - - - @public - def verify() -> bool: - """ - When this contract address is included in the transaction signature, - this method will be triggered as a VerificationTrigger to verify that the signature is correct. - For example, this method needs to be called when withdrawing token from the contract. - - :return: whether the transaction signature is correct - """ - return is_administrator() - - - def is_administrator() -> bool: - """ - Validates if the invoker has administrative rights - - :return: whether the contract's invoker is an administrator - """ - return runtime.check_witness(TOKEN_OWNER) - - - def is_valid_address(address: UInt160) -> bool: - """ - Validates if the address passed through the kyc. - - :return: whether the given address is validated by kyc - """ - return storage.get(KYC_WHITELIST_PREFIX + address).to_int() > 0 - - - @public - def _deploy(data: Any, update: bool): - """ - Initializes the storage when the smart contract is deployed. - - :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. - """ - if not update: - storage.put(TOKEN_TOTAL_SUPPLY_PREFIX, TOKEN_INITIAL_SUPPLY) - storage.put(TOKEN_OWNER, TOKEN_INITIAL_SUPPLY) - - on_transfer(None, TOKEN_OWNER, TOKEN_INITIAL_SUPPLY) - - - @public - def mint(amount: int) -> bool: - """ - Mints new tokens - - :param amount: the amount of gas to be refunded - :type amount: int - :return: whether the refund was successful - """ - assert amount >= 0 - if not is_administrator(): - return False - - if amount > 0: - current_total_supply = total_supply() - owner_balance = balance_of(TOKEN_OWNER) - - storage.put(TOKEN_TOTAL_SUPPLY_PREFIX, current_total_supply + amount) - storage.put(TOKEN_OWNER, owner_balance + amount) - - on_transfer(None, TOKEN_OWNER, amount) - post_transfer(None, TOKEN_OWNER, amount, None) - return True - - - @public - def refund(address: UInt160, neo_amount: int, gas_amount: int) -> bool: - """ - Refunds an address with given Neo and Gas - - :param address: the address that have the tokens - :type address: UInt160 - :param neo_amount: the amount of neo to be refunded - :type neo_amount: int - :param gas_amount: the amount of gas to be refunded - :type gas_amount: int - :return: whether the refund was successful - """ - assert len(address) == 20 - assert neo_amount > 0 or gas_amount > 0 - - if not is_administrator(): - return False - - if neo_amount > 0: - result = NEO_TOKEN.transfer(runtime.calling_script_hash, address, neo_amount) - if not result: - # due to a current limitation in the neo3-boa, changing the condition to `not result` - # will result in a compiler error - return False - - if gas_amount > 0: - result = GAS_TOKEN.transfer(runtime.calling_script_hash, address, gas_amount) - if not result: - # due to a current limitation in the neo3-boa, changing the condition to `not result` - # will result in a compiler error - return False - - return True - - - # ------------------------------------------- - # Public methods from NEP-17 - # ------------------------------------------- - - - @public(safe=True) - def symbol() -> str: - """ - Gets the symbols of the token. - - This symbol should be short (3-8 characters is recommended), with no whitespace characters or new-lines and should - be limited to the uppercase latin alphabet (i.e. the 26 letters used in English). - This method must always return the same value every time it is invoked. - - :return: a short string symbol of the token managed in this contract. - """ - return TOKEN_SYMBOL - - - @public(safe=True) - def decimals() -> int: - """ - Gets the amount of decimals used by the token. - - E.g. 8, means to divide the token amount by 100,000,000 (10 ^ 8) to get its user representation. - This method must always return the same value every time it is invoked. - - :return: the number of decimals used by the token. - """ - return TOKEN_DECIMALS - - - @public(name='totalSupply', safe=True) - def total_supply() -> int: - """ - Gets the total token supply deployed in the system. - - This number mustn't be in its user representation. E.g. if the total supply is 10,000,000 tokens, this method - must return 10,000,000 * 10 ^ decimals. - - :return: the total token supply deployed in the system. - """ - return storage.get(TOKEN_TOTAL_SUPPLY_PREFIX).to_int() - - - @public(name='balanceOf', safe=True) - def balance_of(account: UInt160) -> int: - """ - Get the current balance of an address - - The parameter account should be a 20-byte address. - - :param account: the account address to retrieve the balance for - :type account: UInt160 - - :return: the token balance of the `account` - :raise AssertionError: raised if `account` length is not 20. - """ - assert len(account) == 20 - return storage.get(account).to_int() - - - @public - def transfer(from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: - """ - Transfers a specified amount of NEP17 tokens from one account to another - - If the method succeeds, it must fire the `transfer` event and must return true, even if the amount is 0, - or from and to are the same address. - - :param from_address: the address to transfer from - :type from_address: UInt160 - :param to_address: the address to transfer to - :type to_address: UInt160 - :param amount: the amount of NEP17 tokens to transfer - :type amount: int - :param data: whatever data is pertinent to the onPayment method - :type data: Any - - :return: whether the transfer was successful - :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` if less than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(from_address) == 20 and len(to_address) == 20 - # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. - assert amount >= 0 - - # The function MUST return false if the from account balance does not have enough tokens to spend. - from_balance = storage.get(from_address).to_int() - if from_balance < amount: - return False - - # The function should check whether the from address equals the caller contract hash. - # If so, the transfer should be processed; - # If not, the function should use the check_witness to verify the transfer. - if from_address != runtime.calling_script_hash: - if not runtime.check_witness(from_address): - return False - - # skip balance changes if transferring to yourself or transferring 0 cryptocurrency - if from_address != to_address and amount != 0: - if from_balance == amount: - storage.delete(from_address) - else: - storage.put(from_address, from_balance - amount) - - to_balance = storage.get(to_address).to_int() - storage.put(to_address, to_balance + amount) - - # if the method succeeds, it must fire the transfer event - on_transfer(from_address, to_address, amount) - # if the to_address is a smart contract, it must call the contracts onPayment - post_transfer(from_address, to_address, amount, data) - # and then it must return true - return True - - - def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any): - """ - Checks if the one receiving NEP17 tokens is a smart contract and if it's one the onPayment method will be called - - :param from_address: the address of the sender - :type from_address: UInt160 - :param to_address: the address of the receiver - :type to_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - """ - if to_address is not None: - contract = ContractManagement.get_contract(to_address) - if contract is not None: - call_contract(to_address, 'onNEP17Payment', [from_address, amount, data]) - - - @public - def allowance(from_address: UInt160, to_address: UInt160) -> int: - """ - Returns the amount of tokens that the to account can transfer from the from account. - - :param from_address: the address that have the tokens - :type from_address: UInt160 - :param to_address: the address that is authorized to use the tokens - :type to_address: UInt160 - - :return: the amount of tokens that the `to` account can transfer from the `from` account - :raise AssertionError: raised if `from_address` or `to_address` length is not 20. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(from_address) == 20 and len(to_address) == 20 - return storage.get(TRANSFER_ALLOWANCE_PREFIX + from_address + to_address).to_int() - - - @public(name='transferFrom') - def transfer_from(originator: UInt160, from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: - """ - Transfers an amount from the `from` account to the `to` account if the `originator` has been approved to transfer - the requested amount. - - :param originator: the address where the actual token is - :type originator: UInt160 - :param from_address: the address to transfer from with originator's approval - :type from_address: UInt160 - :param to_address: the address to transfer to - :type to_address: UInt160 - :param amount: the amount of NEP17 tokens to transfer - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - - :return: whether the transfer was successful - :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` if less than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(originator) == 20 and len(from_address) == 20 and len(to_address) == 20 - # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. - assert amount >= 0 - - # The function should check whether the from address equals the caller contract hash. - # If so, the transfer should be processed; - # If not, the function should use the check_witness to verify the transfer. - if from_address != runtime.calling_script_hash: - if not runtime.check_witness(from_address): - return False - - approved_transfer_amount = allowance(originator, from_address) - if approved_transfer_amount < amount: - return False - - originator_balance = balance_of(originator) - if originator_balance < amount: - return False - - # update allowance between originator and from - if approved_transfer_amount == amount: - storage.delete(TRANSFER_ALLOWANCE_PREFIX + originator + from_address) - else: - storage.put(TRANSFER_ALLOWANCE_PREFIX + originator + from_address, approved_transfer_amount - amount) - - # skip balance changes if transferring to yourself or transferring 0 cryptocurrency - if amount != 0 and from_address != to_address: - # update originator's balance - if originator_balance == amount: - storage.delete(originator) - else: - storage.put(originator, originator_balance - amount) - - # updates to's balance - to_balance = storage.get(to_address).to_int() - storage.put(to_address, to_balance + amount) - - # if the method succeeds, it must fire the transfer event - on_transfer(from_address, to_address, amount) - # if the to_address is a smart contract, it must call the contracts onPayment - post_transfer(from_address, to_address, amount, data) - # and then it must return true - return True - - - @public - def approve(originator: UInt160, to_address: UInt160, amount: int) -> bool: - """ - Approves the to account to transfer amount tokens from the originator account. - - :param originator: the address that have the tokens - :type originator: UInt160 - :param to_address: the address that is authorized to use the tokens - :type to_address: UInt160 - :param amount: the amount of NEP17 tokens to transfer - :type amount: int - - :return: whether the approval was successful - :raise AssertionError: raised if `originator` or `to_address` length is not 20 or if `amount` if less than zero. - """ - assert len(originator) == 20 and len(to_address) == 20 - assert amount >= 0 - - if not runtime.check_witness(originator): - return False - - if originator == to_address: - return False - - if not is_valid_address(originator) or not is_valid_address(to_address): - # one of the address doesn't passed the kyc yet - return False - - if balance_of(originator) < amount: - return False - - storage.put(TRANSFER_ALLOWANCE_PREFIX + originator + to_address, amount) - return True - - - # ------------------------------------------- - # Public methods from KYC - # ------------------------------------------- - - - @public - def kyc_register(addresses: List[UInt160]) -> int: - """ - Includes the given addresses to the kyc whitelist - - :param addresses: a list with the addresses to be included - :return: the number of included addresses - """ - included_addresses = 0 - if is_administrator(): - for address in addresses: - if len(address) == 20: - kyc_key = KYC_WHITELIST_PREFIX + address - storage.put(kyc_key, True) - included_addresses += 1 - - return included_addresses - - - @public - def kyc_remove(addresses: List[UInt160]) -> int: - """ - Removes the given addresses from the kyc whitelist - - :param addresses: a list with the addresses to be removed - :return: the number of removed addresses - """ - removed_addresses = 0 - if is_administrator(): - for address in addresses: - if len(address) == 20: - kyc_key = KYC_WHITELIST_PREFIX + address - storage.delete(kyc_key) - removed_addresses += 1 - - return removed_addresses - -2.5 Wrapped Token -================= - -.. warning:: - - **CONTENT MISSING:** Brief Tutorial Description of Wrapped Token - -:: - - from typing import Any, Union - - from boa3.builtin.compile_time import CreateNewEvent, NeoMetadata, metadata, public - from boa3.builtin.contract import Nep17TransferEvent, abort - from boa3.builtin.interop import runtime, storage - from boa3.builtin.interop.contract import GAS as GAS_SCRIPT, NEO as NEO_SCRIPT, call_contract - from boa3.builtin.nativecontract.contractmanagement import ContractManagement - from boa3.builtin.nativecontract.neo import NEO as NEO_TOKEN - from boa3.builtin.type import UInt160 - - - # ------------------------------------------- - # METADATA - # ------------------------------------------- - - @metadata - def manifest_metadata() -> NeoMetadata: - """ - Defines this smart contract's metadata information - """ - meta = NeoMetadata() - meta.supported_standards = ['NEP-17'] - meta.add_permission(methods=['onNEP17Payment']) - # this contract needs to call NEO methods - meta.add_permission(contract='0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5') - - meta.author = "Mirella Medeiros, Ricardo Prado and Lucas Uezu. COZ in partnership with Simpli" - meta.description = "Wrapped NEO Example" - meta.email = "contact@coz.io" - return meta - - - # ------------------------------------------- - # TOKEN SETTINGS - # ------------------------------------------- - - - # Script hash of the contract owner - OWNER = UInt160() - SUPPLY_KEY = 'totalSupply' - - # Symbol of the Token - TOKEN_SYMBOL = 'zNEO' - - # Number of decimal places - TOKEN_DECIMALS = 8 - - # Total Supply of tokens in the system - TOKEN_TOTAL_SUPPLY = 10_000_000 * 10 ** TOKEN_DECIMALS # 10m total supply * 10^8 (decimals) - - # Allowance - ALLOWANCE_PREFIX = b'allowance' - - # ------------------------------------------- - # Events - # ------------------------------------------- - - - on_transfer = Nep17TransferEvent - on_approval = CreateNewEvent( - [ - ('owner', UInt160), - ('spender', UInt160), - ('amount', int) - ], - 'Approval' - ) - - - # ------------------------------------------- - # Methods - # ------------------------------------------- - - - @public(safe=True) - def symbol() -> str: - """ - Gets the symbols of the token. - - This string must be valid ASCII, must not contain whitespace or control characters, should be limited to uppercase - Latin alphabet (i.e. the 26 letters used in English) and should be short (3-8 characters is recommended). - This method must always return the same value every time it is invoked. - - :return: a short string representing symbol of the token managed in this contract. - """ - return TOKEN_SYMBOL - - - @public(safe=True) - def decimals() -> int: - """ - Gets the amount of decimals used by the token. - - E.g. 8, means to divide the token amount by 100,000,000 (10 ^ 8) to get its user representation. - This method must always return the same value every time it is invoked. - - :return: the number of decimals used by the token. - """ - return TOKEN_DECIMALS - - - @public(name='totalSupply', safe=True) - def total_supply() -> int: - """ - Gets the total token supply deployed in the system. - - This number must not be in its user representation. E.g. if the total supply is 10,000,000 tokens, this method - must return 10,000,000 * 10 ^ decimals. - - :return: the total token supply deployed in the system. - """ - return storage.get(SUPPLY_KEY).to_int() - - - @public(name='balanceOf', safe=True) - def balance_of(account: UInt160) -> int: - """ - Get the current balance of an address. - - The parameter account must be a 20-byte address represented by a UInt160. - - :param account: the account address to retrieve the balance for - :type account: bytes - """ - assert len(account) == 20 - return storage.get(account).to_int() - - - @public - def transfer(from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: - """ - Transfers an amount of zNEO tokens from one account to another. - - If the method succeeds, it must fire the `Transfer` event and must return true, even if the amount is 0, - or from and to are the same address. - - :param from_address: the address to transfer from - :type from_address: UInt160 - :param to_address: the address to transfer to - :type to_address: UInt160 - :param amount: the amount of zNEO tokens to transfer - :type amount: int - :param data: whatever data is pertinent to the onPayment method - :type data: Any - - :return: whether the transfer was successful - :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` if less than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(from_address) == 20 and len(to_address) == 20 - # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. - assert amount >= 0 - - # The function MUST return false if the from account balance does not have enough tokens to spend. - from_balance = storage.get(from_address).to_int() - if from_balance < amount: - return False - - # The function should check whether the from address equals the caller contract hash. - # If so, the transfer should be processed; - # If not, the function should use the check_witness to verify the transfer. - if from_address != runtime.calling_script_hash: - if not runtime.check_witness(from_address): - return False - - # skip balance changes if transferring to yourself or transferring 0 cryptocurrency - if from_address != to_address and amount != 0: - if from_balance == amount: - storage.delete(from_address) - else: - storage.put(from_address, from_balance - amount) - - to_balance = storage.get(to_address).to_int() - storage.put(to_address, to_balance + amount) - - # if the method succeeds, it must fire the transfer event - on_transfer(from_address, to_address, amount) - # if the to_address is a smart contract, it must call the contracts onPayment - post_transfer(from_address, to_address, amount, data, True) - # and then it must return true - return True - - - @public(name='transferFrom') - def transfer_from(spender: UInt160, from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: - """ - A spender transfers an amount of zNEO tokens allowed from one account to another. - - If the method succeeds, it must fire the `Transfer` event and must return true, even if the amount is 0, - or from and to are the same address. - - :param spender: the address that is trying to transfer zNEO tokens - :type spender: UInt160 - :param from_address: the address to transfer from - :type from_address: UInt160 - :param to_address: the address to transfer to - :type to_address: UInt160 - :param amount: the amount of zNEO tokens to transfer - :type amount: int - :param data: whatever data is pertinent to the onPayment method - :type data: Any - - :return: whether the transfer was successful - :raise AssertionError: raised if `spender`, `from_address` or `to_address` length is not 20 or if `amount` is less - than zero. - """ - # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. - assert len(spender) == 20 and len(from_address) == 20 and len(to_address) == 20 - # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. - assert amount >= 0 - - # The function MUST return false if the from account balance does not have enough tokens to spend. - from_balance = storage.get(from_address).to_int() - if from_balance < amount: - return False - - # The function MUST return false if the from account balance does not allow enough tokens to be spent by the spender. - allowed = allowance(from_address, spender) - if allowed < amount: - return False - - # The function should check whether the spender address equals the caller contract hash. - # If so, the transfer should be processed; - # If not, the function should use the check_witness to verify the transfer. - if spender != runtime.calling_script_hash: - if not runtime.check_witness(spender): - return False - - if allowed == amount: - storage.delete(ALLOWANCE_PREFIX + from_address + spender) - else: - storage.put(ALLOWANCE_PREFIX + from_address + spender, allowed - amount) - - # skip balance changes if transferring to yourself or transferring 0 cryptocurrency - if from_address != to_address and amount != 0: - if from_balance == amount: - storage.delete(from_address) - else: - storage.put(from_address, from_balance - amount) - - to_balance = storage.get(to_address).to_int() - storage.put(to_address, to_balance + amount) - - # if the method succeeds, it must fire the transfer event - on_transfer(from_address, to_address, amount) - # if the to_address is a smart contract, it must call the contracts onPayment - post_transfer(from_address, to_address, amount, data, True) - # and then it must return true - return True - - - @public - def approve(spender: UInt160, amount: int) -> bool: - """ - Allows spender to spend from your account as many times as they want until it reaches the amount allowed. - The allowed amount will be overwritten if this method is called once more. - - :param spender: the address that will be allowed to use your zNEO - :type spender: UInt160 - :param amount: the total amount of zNEO that the spender can spent - :type amount: int - :raise AssertionError: raised if `from_address` length is not 20 or if `amount` if less than zero. - """ - assert len(spender) == 20 - assert amount >= 0 - - if balance_of(runtime.calling_script_hash) >= amount: - storage.put(ALLOWANCE_PREFIX + runtime.calling_script_hash + spender, amount) - on_approval(runtime.calling_script_hash, spender, amount) - return True - return False - - - @public - def allowance(owner: UInt160, spender: UInt160) -> int: - """ - Gets the amount of zNEO from the owner that can be used by the spender. - - :param owner: the address that allowed the spender to spend zNEO - :type owner: UInt160 - :param spender: the address that can spend zNEO from the owner's account - :type spender: UInt160 - """ - return storage.get(ALLOWANCE_PREFIX + owner + spender).to_int() - - - def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any, - call_onPayment: bool): - """ - Checks if the one receiving NEP17 tokens is a smart contract and if it's one the onPayment method will be called. - - :param from_address: the address of the sender - :type from_address: UInt160 - :param to_address: the address of the receiver - :type to_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - :param call_onPayment: whether onPayment should be called or not - :type call_onPayment: bool - """ - if call_onPayment: - if to_address is not None: - contract = ContractManagement.get_contract(to_address) - if contract is not None: - call_contract(to_address, 'onNEP17Payment', [from_address, amount, data]) - - - def mint(account: UInt160, amount: int): - """ - Mints new zNEO tokens. - - :param account: the address of the account that is sending cryptocurrency to this contract - :type account: UInt160 - :param amount: the amount of gas to be refunded - :type amount: int - :raise AssertionError: raised if amount is less than than 0 - """ - assert amount >= 0 - if amount != 0: - current_total_supply = total_supply() - account_balance = balance_of(account) - - storage.put(SUPPLY_KEY, current_total_supply + amount) - storage.put(account, account_balance + amount) - - on_transfer(None, account, amount) - post_transfer(None, account, amount, None, True) - - - @public(safe=True) - def burn(account: UInt160, amount: int): - """ - Burns zNEO tokens. - - :param account: the address of the account that is pulling out cryptocurrency of this contract - :type account: UInt160 - :param amount: the amount of gas to be refunded - :type amount: int - :raise AssertionError: raised if `account` length is not 20, amount is less than than 0 or the account doesn't have - enough zNEO to burn - """ - assert len(account) == 20 - assert amount >= 0 - if runtime.check_witness(account): - if amount != 0: - current_total_supply = total_supply() - account_balance = balance_of(account) - - assert account_balance >= amount - - storage.put(SUPPLY_KEY, current_total_supply - amount) - - if account_balance == amount: - storage.delete(account) - else: - storage.put(account, account_balance - amount) - - on_transfer(account, None, amount) - post_transfer(account, None, amount, None, False) - - NEO_TOKEN.transfer(runtime.executing_script_hash, account, amount) - - - @public - def verify() -> bool: - """ - When this contract address is included in the transaction signature, - this method will be triggered as a VerificationTrigger to verify that the signature is correct. - For example, this method needs to be called when withdrawing token from the contract. - - :return: whether the transaction signature is correct - """ - return runtime.check_witness(OWNER) - - - @public - def _deploy(data: Any, update: bool): - """ - Initializes the storage when the smart contract is deployed. - - :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. - """ - if not update: - storage.put(SUPPLY_KEY, TOKEN_TOTAL_SUPPLY) - storage.put(OWNER, TOKEN_TOTAL_SUPPLY) - - on_transfer(None, OWNER, TOKEN_TOTAL_SUPPLY) - - - @public - def onNEP17Payment(from_address: UInt160, amount: int, data: Any): - """ - If this smart contract receives NEO, it will mint an amount of wrapped NEO - - :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract - :type from_address: UInt160 - :param amount: the amount of cryptocurrency that is being sent to the this smart contract - :type amount: int - :param data: any pertinent data that might validate the transaction - :type data: Any - """ - # Use calling_script_hash to identify if the incoming token is NEO - if runtime.calling_script_hash == NEO_SCRIPT: - mint(from_address, amount) - elif runtime.calling_script_hash == GAS_SCRIPT: - # GAS is minted when transferring NEO - return - else: - abort() diff --git a/requirements_dev.txt b/requirements_dev.txt index 7f61bf7ac..3df1ae408 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,7 +3,8 @@ base58>=1.0.3 bump2version>=1.0.0 coverage>=6.0.1 filelock>=3.10.7 -Sphinx==4.0.0 -sphinx-rtd-theme==0.5.2 +myst-parser==1.0.0 +Sphinx==5.0.0 +sphinx-rtd-theme==1.2.1 twine>=1.10.0 wheel>=0.30.0