Exemplo n.º 1
0
    def test_transfers_with_invoices(self):
        keys = [priv.public_key for priv in generate_keys(5)]

        # Single memo
        memo_instruction, il = self._get_invoice_memo_instruction(TransactionType.SPEND, 10, 2)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction,
                token.transfer(keys[1], keys[2], keys[3], 10),
                token.transfer(keys[2], keys[3], keys[4], 20)
            ],
        )
        creations, payments = parse_transaction(tx, il)
        assert len(creations) == 0

        for i in range(2):
            assert payments[i].sender == keys[1 + i]
            assert payments[i].destination == keys[2 + i]
            assert payments[i].tx_type == TransactionType.SPEND
            assert payments[i].quarks == (1 + i) * 10
            assert payments[i].invoice == Invoice.from_proto(il.invoices[i])
            assert not payments[i].memo

        # Multiple memos
        memo_instruction_1, il1 = self._get_invoice_memo_instruction(TransactionType.SPEND, 10, 1)
        memo_instruction_2, il2 = self._get_invoice_memo_instruction(TransactionType.P2P, 10, 1)

        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction_1,
                token.transfer(keys[1], keys[2], keys[3], 10),
                memo_instruction_2,
                token.transfer(keys[2], keys[3], keys[4], 20),
            ],
        )
        creations, payments = parse_transaction(tx, il1)
        assert len(creations) == 0

        expected_invoices = [il1.invoices[0], None]
        expected_types = [TransactionType.SPEND, TransactionType.P2P]
        for i in range(2):
            assert payments[i].sender == keys[1 + i]
            assert payments[i].destination == keys[2 + i]
            assert payments[i].tx_type == expected_types[i]
            assert payments[i].quarks == (1 + i) * 10
            if expected_invoices[i]:
                assert payments[i].invoice == Invoice.from_proto(expected_invoices[i])
            else:
                assert not payments[i].invoice
            assert not payments[i].memo
Exemplo n.º 2
0
    def test_errors_from_solana_tx(self, instruction_index, exp_op_index, exp_payment_index):
        keys = [pk.public_key for pk in generate_keys(4)]
        tx = solana.Transaction.new(
            keys[0],
            [
                memo.memo_instruction('data'),
                token.transfer(keys[1], keys[2], keys[1], 100),
                token.set_authority(keys[1], keys[1], token.AuthorityType.CLOSE_ACCOUNT, keys[3])
            ]
        )
        tx_id = b'tx_sig'

        errors = TransactionErrors.from_solana_tx(tx, model_pbv4.TransactionError(
            reason=model_pbv4.TransactionError.Reason.INSUFFICIENT_FUNDS,
            instruction_index=instruction_index,
        ), tx_id)
        assert isinstance(errors.tx_error, InsufficientBalanceError)
        assert len(errors.op_errors) == 3
        for i in range(0, len(errors.op_errors)):
            if i == exp_op_index:
                assert isinstance(errors.op_errors[i], InsufficientBalanceError)
            else:
                assert not errors.op_errors[i]

        if exp_payment_index > -1:
            assert len(errors.payment_errors) == 1
            for i in range(0, len(errors.payment_errors)):
                if i == exp_payment_index:
                    assert isinstance(errors.payment_errors[i], InsufficientBalanceError)
                else:
                    assert not errors.payment_errors[i]
        else:
            assert not errors.payment_errors
Exemplo n.º 3
0
    def _submit_solana_earn_batch_tx(
        self, batch: EarnBatch, service_config: tx_pb.GetServiceConfigResponse, commitment: Commitment,
        transfer_sender: Optional[PublicKey] = None,
    ) -> SubmitTransactionResult:
        subsidizer_id = (batch.subsidizer.public_key if batch.subsidizer
                         else PublicKey(service_config.subsidizer_account.value))

        transfer_sender = transfer_sender if transfer_sender else batch.sender.public_key
        instructions = [
            token.transfer(
                transfer_sender,
                earn.destination,
                batch.sender.public_key,
                earn.quarks,
            ) for earn in batch.earns]

        invoices = [earn.invoice for earn in batch.earns if earn.invoice]
        invoice_list = InvoiceList(invoices) if invoices else None
        if batch.memo:
            instructions = [memo.memo_instruction(batch.memo)] + instructions
        elif self._app_index > 0:
            fk = invoice_list.get_sha_224_hash() if invoice_list else b''
            agora_memo = AgoraMemo.new(1, TransactionType.EARN, self._app_index, fk)
            instructions = [memo.memo_instruction(base64.b64encode(agora_memo.val).decode('utf-8'))] + instructions

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if batch.subsidizer:
            signers = [batch.subsidizer, batch.sender]
        else:
            signers = [batch.sender]

        return self._sign_and_submit_solana_tx(signers, tx, commitment, invoice_list=invoice_list,
                                               dedupe_id=batch.dedupe_id)
Exemplo n.º 4
0
    def test_transfers_no_invoices(self):
        keys = [priv.public_key for priv in generate_keys(5)]

        tx = solana.Transaction.new(
            keys[0],
            [
                token.transfer(keys[1], keys[2], keys[3], 10),
                token.transfer(keys[2], keys[3], keys[4], 20),
            ],
        )
        creations, payments = parse_transaction(tx)
        assert len(creations) == 0

        for i in range(2):
            assert payments[i].sender == keys[1 + i]
            assert payments[i].destination == keys[2 + i]
            assert payments[i].tx_type == TransactionType.UNKNOWN
            assert payments[i].quarks == (1 + i) * 10
            assert not payments[i].invoice
            assert not payments[i].memo
Exemplo n.º 5
0
    def test_get_transaction(self, grpc_channel, executor, no_retry_client):
        source, dest = [key.public_key for key in generate_keys(2)]
        transaction_id = b'someid'
        future = executor.submit(no_retry_client.get_transaction,
                                 transaction_id)

        agora_memo = AgoraMemo.new(1, TransactionType.SPEND, 0, b'')
        tx = Transaction.new(PrivateKey.random().public_key, [
            memo.memo_instruction(
                base64.b64encode(agora_memo.val).decode('utf-8')),
            token.transfer(source, dest,
                           PrivateKey.random().public_key, 100),
        ])

        resp = tx_pb_v4.GetTransactionResponse(
            state=tx_pb_v4.GetTransactionResponse.State.SUCCESS,
            item=tx_pb_v4.HistoryItem(
                transaction_id=model_pb_v4.TransactionId(
                    value=transaction_id, ),
                solana_transaction=model_pb_v4.Transaction(
                    value=tx.marshal(), ),
                payments=[
                    tx_pb_v4.HistoryItem.Payment(
                        source=model_pb_v4.SolanaAccountId(value=source.raw),
                        destination=model_pb_v4.SolanaAccountId(
                            value=dest.raw),
                        amount=100,
                    )
                ],
                invoice_list=model_pb_v3.InvoiceList(invoices=[
                    model_pb_v3.Invoice(items=[
                        model_pb_v3.Invoice.LineItem(title='t1', amount=15),
                    ]),
                ])),
        )
        req = self._set_get_transaction_resp(grpc_channel, resp)
        assert req.transaction_id.value == transaction_id

        tx_data = future.result()
        assert tx_data.tx_id == transaction_id
        assert tx_data.transaction_state == TransactionState.SUCCESS
        assert len(tx_data.payments) == 1
        assert not tx_data.error

        p = tx_data.payments[0]
        assert p.sender.raw == source.raw
        assert p.destination.raw == dest.raw
        assert p.tx_type == TransactionType.SPEND
        assert p.quarks == 100
        assert p.invoice.to_proto().SerializeToString(
        ) == resp.item.invoice_list.invoices[0].SerializeToString()
        assert not p.memo
Exemplo n.º 6
0
    def test_from_json_invalid(self):
        with pytest.raises(ValueError) as e:
            CreateAccountRequest.from_json({})
        assert 'solana_transaction' in str(e)

        keys = [key.public_key for key in generate_keys(4)]
        tx = solana.Transaction.new(keys[0], [
            token.transfer(
                keys[1],
                keys[2],
                keys[3],
                20,
            ),
        ])

        with pytest.raises(ValueError) as e:
            CreateAccountRequest.from_json(
                {'solana_transaction': base64.b64encode(tx.marshal())})
        assert 'unexpected payments' in str(e)

        tx = solana.Transaction.new(keys[0], [])
        with pytest.raises(ValueError) as e:
            CreateAccountRequest.from_json(
                {'solana_transaction': base64.b64encode(tx.marshal())})
        assert 'expected exactly 1 creation' in str(e)

        create_assoc_instruction1, assoc1 = token.create_associated_token_account(
            keys[0], keys[1], keys[2])
        create_assoc_instruction2, assoc2 = token.create_associated_token_account(
            keys[0], keys[1], keys[2])
        tx = solana.Transaction.new(keys[0], [
            create_assoc_instruction1,
            token.set_authority(assoc1,
                                assoc1,
                                token.AuthorityType.CLOSE_ACCOUNT,
                                new_authority=keys[0]),
            create_assoc_instruction2,
            token.set_authority(assoc2,
                                assoc2,
                                token.AuthorityType.CLOSE_ACCOUNT,
                                new_authority=keys[0]),
        ])
        with pytest.raises(ValueError) as e:
            CreateAccountRequest.from_json(
                {'solana_transaction': base64.b64encode(tx.marshal())})
        assert 'expected exactly 1 creation' in str(e)
Exemplo n.º 7
0
    def test_with_invalid_instructions(self):
        keys = [priv.public_key for priv in generate_keys(5)]
        invalid_instructions = [
            token.set_authority(keys[1], keys[2], AuthorityType.ACCOUNT_HOLDER, new_authority=keys[3]),
            token.initialize_account(keys[1], keys[2], keys[3]),
            system.create_account(keys[1], keys[2], keys[3], 10, 10),
        ]

        for i in invalid_instructions:
            tx = solana.Transaction.new(
                keys[0],
                [
                    token.transfer(keys[1], keys[2], keys[3], 10),
                    i,
                ]
            )

            with pytest.raises(ValueError):
                parse_transaction(tx)
Exemplo n.º 8
0
    def test_transfer(self):
        public_keys = [key.public_key for key in generate_keys(3)]
        instruction = transfer(public_keys[0], public_keys[1], public_keys[2], 123456789, _token_program)

        assert instruction.data[0] == Command.TRANSFER
        assert instruction.data[1:] == (123456789).to_bytes(8, 'little')

        assert not instruction.accounts[0].is_signer
        assert instruction.accounts[0].is_writable
        assert not instruction.accounts[1].is_signer
        assert instruction.accounts[1].is_writable
        assert instruction.accounts[2].is_signer
        assert instruction.accounts[2].is_writable

        tx = Transaction.unmarshal(Transaction.new(public_keys[0], [instruction]).marshal())
        decompiled = decompile_transfer(tx.message, 0, _token_program)
        assert decompiled.source == public_keys[0]
        assert decompiled.dest == public_keys[1]
        assert decompiled.owner == public_keys[2]
        assert decompiled.amount == 123456789
Exemplo n.º 9
0
    def _submit_solana_payment_tx(
        self, payment: Payment, service_config: tx_pb.GetServiceConfigResponse, commitment: Commitment,
        transfer_source: Optional[PublicKey] = None, create_instructions: List[solana.Instruction] = None,
        create_signer: Optional[PrivateKey] = None,
    ) -> SubmitTransactionResult:
        subsidizer_id = (payment.subsidizer.public_key if payment.subsidizer
                         else PublicKey(service_config.subsidizer_account.value))

        instructions = []
        invoice_list = None
        if payment.memo:
            instructions = [memo.memo_instruction(payment.memo)]
        elif self._app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])
            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            m = AgoraMemo.new(1, payment.tx_type, self._app_index, fk)
            instructions = [memo.memo_instruction(base64.b64encode(m.val).decode('utf-8'))]

        if create_instructions:
            instructions += create_instructions

        sender = transfer_source if transfer_source else payment.sender.public_key
        instructions.append(token.transfer(
            sender,
            payment.destination,
            payment.sender.public_key,
            payment.quarks,
        ))

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if payment.subsidizer:
            signers = [payment.subsidizer, payment.sender]
        else:
            signers = [payment.sender]

        if create_signer:
            signers.append(create_signer)

        return self._sign_and_submit_solana_tx(signers, tx, commitment, invoice_list=invoice_list,
                                               dedupe_id=payment.dedupe_id)
Exemplo n.º 10
0
    def test_with_text_memo(self):
        keys = [priv.public_key for priv in generate_keys(5)]

        # transfers with single memo
        tx = solana.Transaction.new(
            keys[0],
            [
                memo.memo_instruction('1-test'),
                token.transfer(keys[1], keys[2], keys[3], 10),
                token.transfer(keys[2], keys[3], keys[4], 20),
            ]
        )
        creations, payments = parse_transaction(tx)
        assert len(creations) == 0

        for i in range(2):
            assert payments[i].sender == keys[1 + i]
            assert payments[i].destination == keys[2 + i]
            assert payments[i].tx_type == TransactionType.UNKNOWN
            assert payments[i].quarks == (1 + i) * 10
            assert not payments[i].invoice
            assert payments[i].memo == '1-test'

        # transfers with multiple memos
        expected_memos = ['1-test-alpha', '1-test-beta']
        tx = solana.Transaction.new(
            keys[0],
            [
                memo.memo_instruction(expected_memos[0]),
                token.transfer(keys[1], keys[2], keys[3], 10),
                memo.memo_instruction(expected_memos[1]),
                token.transfer(keys[2], keys[3], keys[4], 20),
            ]
        )
        creations, payments = parse_transaction(tx)
        assert len(creations) == 0

        for i in range(2):
            assert payments[i].sender == keys[1 + i]
            assert payments[i].destination == keys[2 + i]
            assert payments[i].tx_type == TransactionType.UNKNOWN
            assert payments[i].quarks == (1 + i) * 10
            assert not payments[i].invoice
            assert payments[i].memo == expected_memos[i]

        # sender create
        create_instructions, addr = self._generate_create(keys[0], keys[1], keys[2])

        inputs = []
        for i in range(2):
            instructions = create_instructions.copy()
            instructions.append(memo.memo_instruction('1-test'))
            instructions.append(token.transfer(keys[3], keys[4], keys[1], 10))

        for idx, i in enumerate(inputs):
            creations, payments = parse_transaction(i)
            assert len(creations) == 1
            assert len(payments) == 1

            assert creations[0].owner == keys[1]
            assert creations[0].address == addr

            assert payments[0].sender == keys[3]
            assert payments[0].destination == keys[4]
            assert payments[0].tx_type == TransactionType.UNKNOWN
            assert payments[0].quarks == 10
            assert not payments[0].invoice
            assert payments[0].memo == '1-test'
Exemplo n.º 11
0
    def test_invalid_memo_combinations(self):
        keys = [priv.public_key for priv in generate_keys(5)]

        # invalid transaction type combinations
        memo_instruction1, _ = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 1)
        for tx_type in [TransactionType.SPEND, TransactionType.P2P]:
            memo_instruction2, _ = self._get_invoice_memo_instruction(tx_type, 10, 1)
            tx = solana.Transaction.new(
                keys[0],
                [
                    memo_instruction1,
                    token.transfer(keys[1], keys[2], keys[3], 10),
                    memo_instruction2,
                    token.transfer(keys[2], keys[3], keys[4], 20),
                ]
            )

            with pytest.raises(ValueError) as e:
                parse_transaction(tx)
            assert 'cannot mix' in str(e)

        # mixed app IDs
        tx = solana.Transaction.new(
            keys[0],
            [
                memo.memo_instruction('1-kik'),
                memo.memo_instruction('1-kin'),
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx)
        assert 'app IDs' in str(e)

        # mixed app indices
        memo_instruction1, _ = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 1)
        memo_instruction2, _ = self._get_invoice_memo_instruction(TransactionType.EARN, 11, 1)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction1,
                memo_instruction2,
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx)
        assert 'app indexes' in str(e)

        # no memos match the invoice list
        il = self._generate_invoice_list(2)
        memo_instruction, il2 = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 1)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction,
                token.transfer(keys[1], keys[2], keys[3], 10),
                memo_instruction,
                token.transfer(keys[2], keys[3], keys[4], 20),
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx, il)
        assert 'exactly one' in str(e)

        # too many memos match the invoice list
        memo_instruction, il = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 2)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction,
                token.transfer(keys[1], keys[2], keys[3], 10),
                memo_instruction,
                token.transfer(keys[2], keys[3], keys[4], 20),
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx, il)
        assert 'exactly one' in str(e)

        # too many transfers for the invoice list
        memo_instruction, il = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 1)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction,
                token.transfer(keys[1], keys[2], keys[3], 10),
                memo_instruction,
                token.transfer(keys[2], keys[3], keys[4], 20),
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx, il)
        assert 'sufficient invoices' in str(e)

        # too few transfers for the invoice list
        memo_instruction, il = self._get_invoice_memo_instruction(TransactionType.EARN, 10, 2)
        tx = solana.Transaction.new(
            keys[0],
            [
                memo_instruction,
                token.transfer(keys[1], keys[2], keys[3], 10),
            ]
        )

        with pytest.raises(ValueError) as e:
            parse_transaction(tx, il)
        assert 'does not match number of transfers referencing the invoice list' in str(e)
Exemplo n.º 12
0
 def _gen_tx():
     sender, dest, owner = generate_keys(3)
     return solana.Transaction.new(_subsidizer,
                                   [token.transfer(sender, dest, owner, 0)])
Exemplo n.º 13
0
    def merge_token_accounts(
        self, private_key: PrivateKey, create_associated_account: bool, commitment: Optional[Commitment] = None,
        subsidizer: Optional[PrivateKey] = None,
    ) -> Optional[bytes]:
        commitment = commitment if commitment else self._default_commitment

        existing_accounts = self._internal_client.resolve_token_accounts(private_key.public_key, True)
        if len(existing_accounts) == 0 or (len(existing_accounts) == 1 and not create_associated_account):
            return None

        dest = existing_accounts[0].account_id
        instructions = []
        signers = [private_key]

        config = self._internal_client.get_service_config()
        if not config.subsidizer_account.value and not subsidizer:
            raise NoSubsidizerError()

        if subsidizer:
            subsidizer_id = subsidizer.public_key
            signers.append(subsidizer)
        else:
            subsidizer_id = PublicKey(config.subsidizer_account.value)

        if create_associated_account:
            create_instruction, assoc = token.create_associated_token_account(
                subsidizer_id,
                private_key.public_key,
                PublicKey(config.token.value),
            )
            if existing_accounts[0].account_id.raw != assoc.raw:
                instructions.append(create_instruction)
                instructions.append(token.set_authority(
                    assoc,
                    private_key.public_key,
                    token.AuthorityType.CLOSE_ACCOUNT,
                    new_authority=subsidizer_id))
                dest = assoc
            elif len(existing_accounts) == 1:
                return None

        for existing_account in existing_accounts:
            if existing_account.account_id == dest:
                continue

            instructions.append(token.transfer(
                existing_account.account_id,
                dest,
                private_key.public_key,
                existing_account.balance,
            ))

            # If no close authority is set, it likely means we don't know it, and can't make any assumptions
            if not existing_account.close_authority:
                continue

            # If the subsidizer is the close authority, we can include the close instruction as they will be ok with
            # signing for it
            #
            # Alternatively, if we're the close authority, we are signing it.
            should_close = False
            for a in [private_key.public_key, subsidizer_id]:
                if existing_account.close_authority == a:
                    should_close = True
                    break

            if should_close:
                instructions.append(token.close_account(
                    existing_account.account_id,
                    existing_account.close_authority,
                    existing_account.close_authority,
                ))

        transaction = solana.Transaction.new(subsidizer_id, instructions)

        result = self._sign_and_submit_solana_tx(signers, transaction, commitment)
        if result.errors and result.errors.tx_error:
            raise result.errors.tx_error

        return result.tx_id