def test_transfer_negative_amount(self): engine = test_engine(has_snapshot=True, default_script=False) engine.load_script(vm.Script(contracts.GasToken().script)) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block gas = contracts.GasToken() with self.assertRaises(ValueError) as context: gas.transfer(engine, types.UInt160.zero(), types.UInt160.zero(), vm.BigInteger(-1), vm.NullStackItem()) self.assertEqual("Can't transfer a negative amount", str(context.exception))
def test_balance_of(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block engine.invoke_syscall_by_name("Neo.Native.Deploy") gas = contracts.GasToken() neo = contracts.NeoToken() deploy_expected_gas = 30_000_000 deploy_expected_neo = 100_000_000 self.assertEqual( deploy_expected_gas, gas.balance_of(engine.snapshot, self.validator_account) / gas.factor) self.assertEqual( deploy_expected_neo, neo.balance_of(engine.snapshot, self.validator_account)) self.assertEqual(vm.BigInteger.zero(), gas.balance_of(engine.snapshot, types.UInt160.zero())) self.assertEqual(vm.BigInteger.zero(), neo.balance_of(engine.snapshot, types.UInt160.zero()))
def test_transfer_partial_balance_to_account_with_balance(self): gas = contracts.GasToken() manifest = contracts.ContractManifest("contract_name") nef = contracts.NEF(script=b'\x40') state_to = contracts.ContractState( 1, nef, manifest, 0, contract_hash(types.UInt160.zero(), nef.checksum, manifest.name)) account_to = state_to.hash storage_key_to = gas.key_account + account_to account_to_state = gas._state() account_to_state.balance = vm.BigInteger(100) storage_item_to = storage.StorageItem(account_to_state.to_array()) account_from = types.UInt160(b'\x01' * 20) storage_key_from = gas.key_account + account_from account_from_state = gas._state() account_from_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_from_state.to_array()) amount = vm.BigInteger(50) engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the source and destination account have balances engine.snapshot.storages.put(storage_key_from, storage_item_from) engine.snapshot.storages.put(storage_key_to, storage_item_to) transfer_event = () def notify_listener(contract_script_hash, event, state): nonlocal transfer_event transfer_event = (contract_script_hash, event, state) msgrouter.interop_notify += notify_listener engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertTrue(result) self.assertEqual(gas.hash, transfer_event[0]) self.assertEqual("Transfer", transfer_event[1]) state_items = list(transfer_event[2]) self.assertEqual(account_from, types.UInt160(state_items[0].to_array())) self.assertEqual(account_to, types.UInt160(state_items[1].to_array())) self.assertEqual(amount, state_items[2].to_biginteger()) # validate from account is deducted by `amount` new_storage_account_from = engine.snapshot.storages.get( storage_key_from) new_account_state_from = gas._state.deserialize_from_bytes( new_storage_account_from.value) self.assertEqual(account_from_state.balance - amount, new_account_state_from.balance) # validate to account is credited with `amount` new_storage_account_to = engine.snapshot.storages.get(storage_key_to) new_account_state_to = gas._state.deserialize_from_bytes( new_storage_account_to.value) self.assertEqual(account_to_state.balance + amount, new_account_state_to.balance)
def test_total_supply(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block engine.invoke_syscall_by_name("Neo.Native.Deploy") gas = contracts.GasToken() neo = contracts.NeoToken() self.assertEqual(30_000_000 * gas.factor, gas.total_supply(engine.snapshot)) self.assertEqual(100_000_000, neo.total_supply(engine.snapshot))
def test_to_account_not_payable(self): gas = contracts.GasToken() state = storage.ContractState(b'\x00', contracts.ContractManifest()) engine = self.transfer_helper(gas, types.UInt160.zero(), state.script_hash(), vm.BigInteger(1)) # default manifest is not payable engine.snapshot.contracts.put(state) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def init(self): self._methods: Dict[int, _NativeMethodMeta] = {} # offset, meta self._management = contracts.ManagementContract() self._neo = contracts.NeoToken() self._gas = contracts.GasToken() self._policy = contracts.PolicyContract() self._oracle = contracts.OracleContract() self._ledger = contracts.LedgerContract() self._role = contracts.DesignationContract() self._crypto = contracts.CryptoContract() self._stdlib = contracts.StdLibContract() # Find all methods that have been augmented by the @register decorator # and turn them into methods that can be called by VM scripts methods_meta = [] for pair in inspect.getmembers(self, lambda m: hasattr(m, "native_call")): methods_meta.append(_NativeMethodMeta(pair[1])) methods_meta.sort( key=lambda x: (x.descriptor.name, len(x.descriptor.parameters))) sb = vm.ScriptBuilder() for meta in methods_meta: meta.descriptor.offset = len(sb) sb.emit_push(0) self._methods.update({len(sb): meta}) sb.emit_syscall(1736177434) # "System.Contract.CallNative" sb.emit(vm.OpCode.RET) self._script: bytes = sb.to_array() self.nef = contracts.NEF("neo-core-v3.0", self._script) sender = types.UInt160.zero() # OpCode.PUSH1 sb = vm.ScriptBuilder() sb.emit(vm.OpCode.ABORT) sb.emit_push(sender.to_array()) sb.emit_push(0) sb.emit_push(self.service_name()) self._hash: types.UInt160 = to_script_hash(sb.to_array()) self._manifest: contracts.ContractManifest = contracts.ContractManifest( ) self._manifest.name = self.service_name() self._manifest.abi.methods = list( map(lambda m: m.descriptor, methods_meta)) if self._id != NativeContract._id: self._contracts.update({self.service_name(): self}) self._contract_hashes.update({self._hash: self}) self.active_block_index = settings.native_contract_activation.get( self.service_name, 0)
def test_total_supply(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block gas = contracts.GasToken() neo = contracts.NeoToken() # this is now the initial stored value + the result of the post_persist event (=committee reward added) expected_total_gas_supply = vm.BigInteger(3000000050000000) self.assertEqual(expected_total_gas_supply, gas.total_supply(engine.snapshot)) self.assertEqual(100_000_000, neo.total_supply(engine.snapshot))
def test_transfer_from_empty_account(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state = storage.ContractState(b'\x00', manifest) engine = self.transfer_helper(gas, types.UInt160.zero(), state.script_hash(), vm.BigInteger(1)) engine.snapshot.contracts.put(state) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def test_transfer_from_empty_account(self): gas = contracts.GasToken() manifest = contracts.ContractManifest("test_contract2") nef = contracts.NEF(script=b'\x40') state = contracts.ContractState( 2, nef, manifest, 0, contract_hash(types.UInt160.zero(), nef.checksum, manifest.name)) engine = self.transfer_helper(gas, types.UInt160.zero(), state.hash, vm.BigInteger(1)) engine.snapshot.contracts.put(state) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def init(self, backend: storage.IDBImplementation = None, store_genesis_block=True): # type: ignore self.backend = backend if backend else settings.database self._current_snapshot = None self.genesis_block = self._create_genesis_block() self.genesis_block.rebuild_merkle_root() sb = vm.ScriptBuilder() for c in [contracts.GasToken(), contracts.NeoToken()]: sb.emit_contract_call(c.script_hash, "onPersist") # type: ignore sb.emit(vm.OpCode.DROP) self.native_onpersist_script = sb.to_array() if self.currentSnapshot.block_height < 0 and store_genesis_block: self.persist(self.genesis_block)
def test_transfer_fail_no_permission(self): """ Test to transfer tokens from a source account that is not owned by the smart contract that is asking for the transfer. We do not add a witness that approves that we can transfer from the source account, thus it should fail Returns: """ gas = contracts.GasToken() engine = self.transfer_helper(gas, types.UInt160.zero(), types.UInt160.zero(), vm.BigInteger(1)) engine.script_container.script_hashes = [ ] # ensure checkwitness returns False engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def test_burn(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block gas = contracts.GasToken() with self.assertRaises(ValueError) as context: gas.burn(engine, self.validator_account, vm.BigInteger(-1)) self.assertEqual("Can't burn a negative amount", str(context.exception)) default_gas = 30_000_000 self.assertEqual( default_gas, gas.balance_of(engine.snapshot, self.validator_account) / gas.factor) gas.burn(engine, self.validator_account, vm.BigInteger(0)) self.assertEqual( default_gas, gas.balance_of(engine.snapshot, self.validator_account) / gas.factor) with self.assertRaises(ValueError) as context: gas.burn(engine, self.validator_account, vm.BigInteger(default_gas + 1) * gas.factor) self.assertEqual( "Insufficient balance. Requesting to burn 3000000100000000, available 3000000000000000", str(context.exception)) # burn a bit gas.burn(engine, self.validator_account, vm.BigInteger(10) * gas.factor) remaining_balance = int( gas.balance_of(engine.snapshot, self.validator_account) / gas.factor) self.assertEqual(default_gas - 10, remaining_balance) # now burn it all gas.burn(engine, self.validator_account, vm.BigInteger(remaining_balance) * gas.factor) self.assertEqual( 0, gas.balance_of(engine.snapshot, self.validator_account) / gas.factor)
def test_transfer_full_balance(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00' * 20, manifest) account_to = state_to.script_hash() account_from = types.UInt160(b'\x01' * 20) storage_key_from = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_from.to_array()) account_from_state = gas._state() account_from_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_from_state.to_array()) amount = account_from_state.balance engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the destination contract exists engine.snapshot.contracts.put(state_to) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) transfer_event = () def notify_listener(contract_script_hash, event, state): nonlocal transfer_event transfer_event = (contract_script_hash, event, state) msgrouter.interop_notify += notify_listener engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertTrue(result) self.assertEqual(gas.script_hash, transfer_event[0]) self.assertEqual("Transfer", transfer_event[1]) state_items = list(transfer_event[2]) self.assertEqual(account_from, types.UInt160(state_items[0].to_array())) self.assertEqual(account_to, types.UInt160(state_items[1].to_array())) self.assertEqual(amount, state_items[2].to_biginteger()) # test that the source account is no longer present in storage as the balance is zero self.assertIsNone(engine.snapshot.storages.try_get(storage_key_from))
def test_transfer_full_balance(self): gas = contracts.GasToken() manifest = contracts.ContractManifest("contract_name_to") nef = contracts.NEF(script=b'\x40') state_to = contracts.ContractState( 1, nef, manifest, 0, contract_hash(types.UInt160.zero(), nef.checksum, manifest.name)) account_to = state_to.hash account_from = types.UInt160(b'\x01' * 20) storage_key_from = gas.key_account + account_from account_from_state = gas._state() account_from_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_from_state.to_array()) amount = account_from_state.balance engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) transfer_event = () def notify_listener(contract_script_hash, event, state): nonlocal transfer_event transfer_event = (contract_script_hash, event, state) msgrouter.interop_notify += notify_listener engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertTrue(result) self.assertEqual(gas.hash, transfer_event[0]) self.assertEqual("Transfer", transfer_event[1]) state_items = list(transfer_event[2]) self.assertEqual(account_from, types.UInt160(state_items[0].to_array())) self.assertEqual(account_to, types.UInt160(state_items[1].to_array())) self.assertEqual(amount, state_items[2].to_biginteger()) # test that the source account is no longer present in storage as the balance is zero self.assertIsNone(engine.snapshot.storages.try_get(storage_key_from))
def test_burn(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block engine.invoke_syscall_by_name("Neo.Native.Deploy") gas = contracts.GasToken() with self.assertRaises(ValueError) as context: gas.burn(engine, self.validator_account, vm.BigInteger(-1)) self.assertEqual("Can't burn a negative amount", str(context.exception)) # burning nothing should not change the total supply default_gas = 30_000_000 self.assertEqual(default_gas, gas.total_supply(engine.snapshot) / gas.factor) gas.burn(engine, self.validator_account, vm.BigInteger(0)) self.assertEqual(default_gas, gas.total_supply(engine.snapshot) / gas.factor) # Note: our account holds the total supply with self.assertRaises(ValueError) as context: gas.burn(engine, self.validator_account, vm.BigInteger(default_gas + 1) * gas.factor) self.assertEqual( "Insufficient balance. Requesting to burn 3000000100000000, available 3000000000000000", str(context.exception)) # burn a bit gas.burn(engine, self.validator_account, vm.BigInteger(10) * gas.factor) remaining_total_supply = int( gas.total_supply(engine.snapshot) / gas.factor) self.assertEqual(default_gas - 10, remaining_total_supply) # now burn it all gas.burn(engine, self.validator_account, vm.BigInteger(remaining_total_supply) * gas.factor) self.assertEqual(0, gas.total_supply(engine.snapshot) / gas.factor)
def test_transfer_more_than_balance(self): gas = contracts.GasToken() account_from = types.UInt160.zero() storage_key_from = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_from.to_array()) account_state = gas._state() account_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_state.to_array()) manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00', manifest) account_to = state_to.script_hash() amount = account_state.balance + 1 engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the destination contract exists engine.snapshot.contracts.put(state_to) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def test_transfer_more_than_balance(self): gas = contracts.GasToken() account_from = types.UInt160.zero() storage_key_from = gas.key_account + account_from account_state = gas._state() account_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_state.to_array()) manifest_to = contracts.ContractManifest("source_contract") nef_to = contracts.NEF(script=b'\x40') state_to = contracts.ContractState( 1, nef_to, manifest_to, 0, contract_hash(types.UInt160.zero(), nef_to.checksum, manifest_to.name)) account_to = state_to.hash amount = account_state.balance + 1 engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def test_on_persist(self): """ OnPersist will do the following * burn the system and network fees for all transactions * mint the sum of network_fees for all transactions to the address of the consensus node that acted as primary speaker for the block """ engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block engine.invoke_syscall_by_name("Neo.Native.Deploy") gas = contracts.GasToken() # with the default Application trigger type we're not allowed to call on_persist with self.assertRaises(SystemError) as context: gas.on_persist(engine) self.assertEqual("Invalid operation", str(context.exception)) # set correct trigger type or we fail super().on_persist() engine.trigger = contracts.TriggerType.SYSTEM # update the TX signer account to point to our validator or the token burn() (part of on persist) # will fail because it can't find an account with balance mock_signer = mock.MagicMock() mock_signer.account = self.validator_account engine.snapshot.persisting_block.transactions[0].signers = [ mock_signer ] # our consensus_data is not setup in a realistic way, so we have to correct for that here # or we fail to get the account of primary consensus node engine.snapshot.persisting_block.consensus_data.primary_index = settings.network.validators_count - 1 gas.on_persist(engine) """ Drop the below in a test in UT_NativeContract.cs and change ProtocolSettings.cs to * have a ValidatorsCount of 1 * and the StandbyCommittee should be: 02158c4a4810fa2a6a12f7d33d835680429e1a68ae61161c5b3fbc98c7f1f17765 var snapshot = Blockchain.Singleton.GetSnapshot(); snapshot.PersistingBlock = new Block() { Index = 1000 }; var point = ECPoint.Parse("02158c4a4810fa2a6a12f7d33d835680429e1a68ae61161c5b3fbc98c7f1f17765", ECCurve.Secp256r1); var account = Contract.CreateMultiSigRedeemScript(1, new ECPoint[] {point}).ToScriptHash(); var tx = TestUtils.GetTransaction(account); tx.SystemFee = 456; tx.NetworkFee = 789; snapshot.PersistingBlock.Transactions = new Transaction[] {tx}; snapshot.PersistingBlock.ConsensusData = new ConsensusData { PrimaryIndex = 0}; ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.System, tx, snapshot, 0); NativeContract.GAS.OnPersist(engine2); var key = new byte[] {0x14}; var sk = key.Concat(account.ToArray()); var item = engine2.Snapshot.Storages.TryGet(new StorageKey {Id = NativeContract.GAS.Id, Key = sk.ToArray()}); var state = item.GetInteroperable<AccountState>(); Console.WriteLine($"account state {state.Balance}"); var item2 = engine2.Snapshot.Storages.TryGet(new StorageKey {Id = NativeContract.GAS.Id, Key = new byte[]{11}}); Console.WriteLine($"total supply {(BigInteger)item2}"); var primary_account = Contract.CreateSignatureRedeemScript(point).ToScriptHash(); var primary_sk = key.Concat(primary_account.ToArray()); var primary_item = engine2.Snapshot.Storages.TryGet(new StorageKey {Id = NativeContract.GAS.Id, Key = primary_sk.ToArray()}); var primary_state = primary_item.GetInteroperable<AccountState>(); Console.WriteLine($"primary account state {primary_state.Balance}"); """ # * our validator prior to on_persist had a balance of 30_000_000 # * after it should have been reduced by the network + system_fee's paid in the transaction sk_gas_supply = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + self.validator_account.to_array()) si_supply = engine.snapshot.storages.try_get(sk_gas_supply) self.assertIsNotNone(si_supply) token_state = gas._state.deserialize_from_bytes(si_supply.value) total_fees = engine.snapshot.persisting_block.transactions[0].network_fee + \ engine.snapshot.persisting_block.transactions[0].system_fee expected = (30_000_000 * gas.factor) - total_fees self.assertEqual(expected, int(token_state.balance)) # * total GAS supply was 30_000_000, should be reduced by the system_fee sk_total_supply = storage.StorageKey(gas.script_hash, gas._PREFIX_TOTAL_SUPPLY) si_total_supply = engine.snapshot.storages.try_get(sk_total_supply) self.assertIsNotNone(si_total_supply) expected = ( 30_000_000 * gas.factor ) - engine.snapshot.persisting_block.transactions[0].system_fee self.assertEqual(expected, vm.BigInteger(si_total_supply.value)) # * the persisting block contains exactly 1 transaction # * after on_persist the account our primary validator should have been credited with the transaction's # network_fee primary_validator = to_script_hash( contracts.Contract.create_signature_redeemscript( self.validator_public_key)) sk_gas_supply = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + primary_validator.to_array()) si_supply = engine.snapshot.storages.try_get(sk_gas_supply) self.assertIsNotNone(si_supply) token_state = gas._state.deserialize_from_bytes(si_supply.value) expected = engine.snapshot.persisting_block.transactions[0].network_fee self.assertEqual(expected, int(token_state.balance))
def test_various(self): native = contracts.NativeContract() known_contracts = native.registered_contracts self.assertIn(contracts.GasToken(), known_contracts) self.assertIn(contracts.NeoToken(), known_contracts) self.assertIn(contracts.PolicyContract(), known_contracts)
def test_negative_mint(self): gas = contracts.GasToken() with self.assertRaises(ValueError) as context: gas.mint(None, None, vm.BigInteger(-1)) self.assertEqual("Can't mint a negative amount", str(context.exception))
def test_token_symbols(self): gas_symbol = contracts.GasToken().symbol() neo_symbol = contracts.NeoToken().symbol() self.assertEqual("gas", gas_symbol) self.assertEqual("neo", neo_symbol)
def test_token_standards(self): gas_standards = contracts.GasToken().supported_standards() neo_standards = contracts.NeoToken().supported_standards() self.assertEqual(["NEP-5"], gas_standards) self.assertEqual(["NEP-5"], neo_standards)