UNPKG

@radixdlt/atom-transaction-mapping

Version:
323 lines (293 loc) 8.87 kB
import { toAddress } from '../../account/test/address.test' import { anyUpParticle, particleGroups, Spin, spunParticle, spunUpParticle, TokenDefinitionParticleBase, Atom, ParticleGroup, FixedSupplyTokenDefinitionParticle, TransferrableTokensParticle, } from '@radixdlt/atom' import { TransferTokensAction, TransferTokensActionInput, TransferTokensActionT, } from '@radixdlt/actions' import { Amount, AmountT, five, Granularity, isAmount, maxAmount, one, three, two, } from '@radixdlt/primitives' import { tokenTransferActionToParticleGroupsMapper } from '../src/toAtom/tokenTransferActionToParticleGroupsMapper' import { syncMapAtomToTokenTransfers as mapAtomToTokenTransfers, pgToTokenTransfer, } from '../src/fromAtom/atomToTokenTransfersMapper' import { UInt256 } from '@radixdlt/uint256' import { TokenTransfer } from '../src/fromAtom/_types' import { unallocatedTokensParticleFromUnsafe } from '../../atom/test/helpers/utility' import { ResourceIdentifierT } from '@radixdlt/atom/src/_types' import { SpunParticleT, TransferrableTokensParticleT, UnallocatedTokensParticleT, } from '@radixdlt/atom/src/particles/_types' import { AddressT } from '@radixdlt/account' describe('AtomToTokenTransfersMapper', () => { const alice = toAddress( '9S8khLHZa6FsyGo634xQo9QwLgSHGpXHHW764D5mPYBcrnfZV6RT', ) const bob = toAddress( '9S9LHeQNFpNJYqLtTJeAbos1LCC5Q7HBiGwPf2oju3NRq5MBKAGt', ) const carol = toAddress( '9S8sKfN3wGyJdfyu9RwWvGKtZqq3R1NaxwT63VXi5dEZ6dUJXLyR', ) const granularity: Granularity = one const fixedSupplyTokenDefinitionParticle_ = FixedSupplyTokenDefinitionParticle.create( { granularity, supply: maxAmount, symbol: 'ALICE', name: 'AliceCoin', address: alice, }, )._unsafeUnwrap() const aliceCoin = fixedSupplyTokenDefinitionParticle_.resourceIdentifier type AmountLike = number | AmountT const makeAmount = (amount: AmountLike): AmountT => isAmount(amount) ? amount : Amount.inSmallestDenomination(UInt256.valueOf(amount)) const makeTTPWithSpin = ( spin: Spin, resourceIdentifier: ResourceIdentifierT, owner: AddressT, amount: AmountLike, ): SpunParticleT<TransferrableTokensParticleT> => { return spunParticle({ particle: TransferrableTokensParticle.create({ amount: makeAmount(amount), granularity, resourceIdentifier: resourceIdentifier, address: owner, })._unsafeUnwrap(), spin: spin, }) } const makeUnallocatedTokenParticleWithSpin = ( spin: Spin, resourceIdentifier: ResourceIdentifierT, amount: AmountLike, ): SpunParticleT<UnallocatedTokensParticleT> => { return spunParticle({ particle: unallocatedTokensParticleFromUnsafe({ amount: makeAmount(amount), granularity, resourceIdentifier: resourceIdentifier, })._unsafeUnwrap(), spin: spin, }) } const upTTP = makeTTPWithSpin.bind(null, Spin.UP) const downTTP = makeTTPWithSpin.bind(null, Spin.DOWN) type TransferTokensActionInputIsh = Omit< TransferTokensActionInput, 'resourceIdentifier' | 'amount' > const makeTransferWithRRI = ( input: TransferTokensActionInputIsh & { amount: AmountLike }, ): ((_: ResourceIdentifierT) => TransferTokensActionT) => ( rri: ResourceIdentifierT, ): TransferTokensActionT => TransferTokensAction.create({ ...input, amount: makeAmount(input.amount), resourceIdentifier: rri, }) const expectSuccess = <T extends TokenDefinitionParticleBase>( tokenDefinitionParticle: T, makeTransfer: (rri: ResourceIdentifierT) => TransferTokensActionT, consumablesFromAmounts: AmountLike[], filterTokenTransfersForAcccount: AddressT, validateTransfers: (tokenTransfers: TokenTransfer[]) => void, ): void => { const resourceID = tokenDefinitionParticle.resourceIdentifier const transferAction: TransferTokensActionT = makeTransfer(resourceID) const actor = transferAction.sender const transferToPGsMapper = tokenTransferActionToParticleGroupsMapper() const upTTPs = consumablesFromAmounts .map(upTTP.bind(null, resourceID, actor)) .map((sp: SpunParticleT<TransferrableTokensParticleT>) => sp.eraseToAny(), ) const upParticles = [ spunUpParticle(tokenDefinitionParticle).eraseToAny(), ].concat(upTTPs) const groups = transferToPGsMapper .particleGroupsFromAction({ action: transferAction, upParticles: upParticles.map((sp) => anyUpParticle(sp.particle), ), addressOfActiveAccount: actor, }) ._unsafeUnwrap() const particleGroups_ = particleGroups(groups) const atom_ = Atom.create({ particleGroups: particleGroups_ }) const tokenTransfers = mapAtomToTokenTransfers({ atom: atom_, addressOfActiveAccount: filterTokenTransfersForAcccount, }) validateTransfers(tokenTransfers) } const validateSingleTransfer = ( expectedSender: AddressT, expectedRecipient: AddressT, expectedAmount: AmountT, expectedResourceIdentifer: ResourceIdentifierT, ): ((_: TokenTransfer[]) => void) => (transfers: TokenTransfer[]): void => { expect(transfers.length).toBe(1) const tokenTransfer = transfers[0] expectAddressesEqual(tokenTransfer.from, expectedSender) expectAddressesEqual(tokenTransfer.to, expectedRecipient) expectAmountsEqual(tokenTransfer.tokenAmount.amount, expectedAmount) expectResourceIdentifiersEqual( tokenTransfer.tokenAmount.token.resourceIdentifier, expectedResourceIdentifer, ) } it('should work with a single transfer with change', () => { const amountToTransfer = five expectSuccess( fixedSupplyTokenDefinitionParticle_, makeTransferWithRRI({ amount: amountToTransfer, from: alice, to: bob, }), [one, two, three], alice, validateSingleTransfer(alice, bob, amountToTransfer, aliceCoin), ) }) it('should work with a single transfer without change', () => { const amountToTransfer = three expectSuccess( fixedSupplyTokenDefinitionParticle_, makeTransferWithRRI({ amount: amountToTransfer, from: alice, to: bob, }), [one, two], alice, validateSingleTransfer(alice, bob, amountToTransfer, aliceCoin), ) }) it('should return empty list when filterering on a third party address', () => { expectSuccess( fixedSupplyTokenDefinitionParticle_, makeTransferWithRRI({ amount: three, from: alice, to: bob, }), [one, two], carol, // <--- Neither sender nor recipient (transfers) => { expect(transfers.length).toBe(0) }, ) }) it('should return one transfer when filterering on recipient', () => { const amountToTransfer = three expectSuccess( fixedSupplyTokenDefinitionParticle_, makeTransferWithRRI({ amount: amountToTransfer, from: alice, to: bob, }), [one, two], bob, // <-- Recipient instead of sender validateSingleTransfer(alice, bob, amountToTransfer, aliceCoin), ) }) const makeUpTTP = upTTP.bind(null, aliceCoin) const makeDownTTP = downTTP.bind(null, aliceCoin) it('should fail with a PG containing 3 participants', () => { const weirdParticleGroup = ParticleGroup.create([ makeUpTTP(alice, five), makeDownTTP(alice, one), makeUpTTP(bob, one), makeDownTTP(alice, two), makeUpTTP(carol, two), ]) const transfersResult = pgToTokenTransfer(weirdParticleGroup) transfersResult.match( () => { throw Error('expected error, but got none') }, (f) => expect(f.message).toBe( 'A transfer should have one or two receivers. Unable to parse.', ), ) }) it('should fail with a PG containing down TTPs from multiple addresses', () => { const weirdParticleGroup = ParticleGroup.create([ makeDownTTP(alice, one), makeDownTTP(bob, two), ]) const transfersResult = pgToTokenTransfer(weirdParticleGroup) transfersResult.match( () => { throw Error('expected error, but got none') }, (f) => expect(f.message).toBe('Incorrect number of senders.'), ) }) it('should fail with a PG containing an UnallocatedTokenParticle', () => { const burnActionParticleGroup = ParticleGroup.create([ makeUpTTP(alice, three), makeDownTTP(alice, three), makeUnallocatedTokenParticleWithSpin( Spin.UP, aliceCoin, three, ).eraseToAny(), ]) const transfersResult = pgToTokenTransfer(burnActionParticleGroup) transfersResult.match( () => { throw Error('expected error, but got none') }, (f) => expect(f.message).toBe( 'Action seems to be a burn action, which we will omit and not count as a transfer.', ), ) }) }) const expectAddressesEqual = (lhs: AddressT, rhs: AddressT): void => { expect(lhs.toString()).toBe(rhs.toString()) } const expectAmountsEqual = (lhs: AmountT, rhs: AmountT): void => { expect(lhs.toString()).toBe(rhs.toString()) } const expectResourceIdentifiersEqual = ( lhs: ResourceIdentifierT, rhs: ResourceIdentifierT, ): void => { expect(lhs.toString()).toBe(rhs.toString()) }