mirror of
				https://github.com/Xahau/xahau.js.git
				synced 2025-11-04 04:55:48 +00:00 
			
		
		
		
	* Add Clawback transaction * Account flag lsfAllowTrustLineClawback * Support bitwise flag checking of 64 bit flags Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com>
This commit is contained in:
		@@ -167,6 +167,7 @@ fixXahauV1
 | 
			
		||||
fixXahauV2
 | 
			
		||||
fixXahauV3
 | 
			
		||||
PaychanAndEscrowForTokens
 | 
			
		||||
Clawback
 | 
			
		||||
 | 
			
		||||
[network_id]
 | 
			
		||||
21337
 | 
			
		||||
 
 | 
			
		||||
@@ -2829,6 +2829,7 @@
 | 
			
		||||
    "NFTokenCreateOffer": 27,
 | 
			
		||||
    "NFTokenCancelOffer": 28,
 | 
			
		||||
    "NFTokenAcceptOffer": 29,
 | 
			
		||||
    "Clawback": 30,
 | 
			
		||||
    "URITokenMint": 45,
 | 
			
		||||
    "URITokenBurn": 46,
 | 
			
		||||
    "URITokenBuy": 47,
 | 
			
		||||
 
 | 
			
		||||
@@ -152,6 +152,11 @@ export interface AccountRootFlagsInterface {
 | 
			
		||||
   * Disallow incoming Remit from other accounts.
 | 
			
		||||
   */
 | 
			
		||||
  lsfDisallowIncomingRemit?: boolean
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This address can claw back issued IOUs. Once enabled, cannot be disabled.
 | 
			
		||||
   */
 | 
			
		||||
  lsfAllowTrustLineClawback?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum AccountRootFlags {
 | 
			
		||||
@@ -216,4 +221,8 @@ export enum AccountRootFlags {
 | 
			
		||||
   * Disallow incoming Remits from other accounts.
 | 
			
		||||
   */
 | 
			
		||||
  lsfDisallowIncomingRemit = 0x80000000,
 | 
			
		||||
  /**
 | 
			
		||||
   * This address can claw back issued IOUs. Once enabled, cannot be disabled.
 | 
			
		||||
   */
 | 
			
		||||
  lsfAllowTrustLineClawback = 0x00001000,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,8 @@ export enum AccountSetAsfFlags {
 | 
			
		||||
  asfDisallowIncomingTrustline = 15,
 | 
			
		||||
  /** Disallow other accounts from sending incoming Remits */
 | 
			
		||||
  asfDisallowIncomingRemit = 16,
 | 
			
		||||
  /** Permanently gain the ability to claw back issued IOUs */
 | 
			
		||||
  asfAllowTrustLineClawback = 17,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								packages/xahau/src/models/transactions/clawback.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/xahau/src/models/transactions/clawback.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
import { ValidationError } from '../../errors'
 | 
			
		||||
import { IssuedCurrencyAmount } from '../common'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  BaseTransaction,
 | 
			
		||||
  validateBaseTransaction,
 | 
			
		||||
  isIssuedCurrency,
 | 
			
		||||
} from './common'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The Clawback transaction is used by the token issuer to claw back
 | 
			
		||||
 * issued tokens from a holder.
 | 
			
		||||
 */
 | 
			
		||||
export interface Clawback extends BaseTransaction {
 | 
			
		||||
  TransactionType: 'Clawback'
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates the AccountID that submitted this transaction. The account MUST
 | 
			
		||||
   * be the issuer of the currency.
 | 
			
		||||
   */
 | 
			
		||||
  Account: string
 | 
			
		||||
  /**
 | 
			
		||||
   * The amount of currency to deliver, and it must be non-XRP. The nested field
 | 
			
		||||
   * names MUST be lower-case. The `issuer` field MUST be the holder's address,
 | 
			
		||||
   * whom to be clawed back.
 | 
			
		||||
   */
 | 
			
		||||
  Amount: IssuedCurrencyAmount
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Verify the form and type of an Clawback at runtime.
 | 
			
		||||
 *
 | 
			
		||||
 * @param tx - An Clawback Transaction.
 | 
			
		||||
 * @throws When the Clawback is Malformed.
 | 
			
		||||
 */
 | 
			
		||||
export function validateClawback(tx: Record<string, unknown>): void {
 | 
			
		||||
  validateBaseTransaction(tx)
 | 
			
		||||
 | 
			
		||||
  if (tx.Amount == null) {
 | 
			
		||||
    throw new ValidationError('Clawback: missing field Amount')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!isIssuedCurrency(tx.Amount)) {
 | 
			
		||||
    throw new ValidationError('Clawback: invalid Amount')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) {
 | 
			
		||||
    throw new ValidationError('Clawback: invalid holder Account')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -61,3 +61,4 @@ export { URITokenCreateSellOffer } from './uriTokenCreateSellOffer'
 | 
			
		||||
export { URITokenBuy } from './uriTokenBuy'
 | 
			
		||||
export { URITokenCancelSellOffer } from './uriTokenCancelSellOffer'
 | 
			
		||||
export { UNLModify } from './UNLModify'
 | 
			
		||||
export { Clawback } from './clawback'
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import { CheckCancel, validateCheckCancel } from './checkCancel'
 | 
			
		||||
import { CheckCash, validateCheckCash } from './checkCash'
 | 
			
		||||
import { CheckCreate, validateCheckCreate } from './checkCreate'
 | 
			
		||||
import { ClaimReward, validateClaimReward } from './claimReward'
 | 
			
		||||
import { Clawback, validateClawback } from './clawback'
 | 
			
		||||
import { BaseTransaction, isIssuedCurrency } from './common'
 | 
			
		||||
import { DepositPreauth, validateDepositPreauth } from './depositPreauth'
 | 
			
		||||
import { EnableAmendment } from './enableAmendment'
 | 
			
		||||
@@ -66,6 +67,7 @@ export type SubmittableTransaction =
 | 
			
		||||
  | CheckCash
 | 
			
		||||
  | CheckCreate
 | 
			
		||||
  | ClaimReward
 | 
			
		||||
  | Clawback
 | 
			
		||||
  | DepositPreauth
 | 
			
		||||
  | EscrowCancel
 | 
			
		||||
  | EscrowCreate
 | 
			
		||||
@@ -204,6 +206,10 @@ export function validate(transaction: Record<string, unknown>): void {
 | 
			
		||||
      validateClaimReward(tx)
 | 
			
		||||
      break
 | 
			
		||||
 | 
			
		||||
    case 'Clawback':
 | 
			
		||||
      validateClawback(tx)
 | 
			
		||||
      break
 | 
			
		||||
 | 
			
		||||
    case 'DepositPreauth':
 | 
			
		||||
      validateDepositPreauth(tx)
 | 
			
		||||
      break
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								packages/xahau/test/integration/transactions/clawback.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/xahau/test/integration/transactions/clawback.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
import { assert } from 'chai'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  AccountSet,
 | 
			
		||||
  AccountSetAsfFlags,
 | 
			
		||||
  TrustSet,
 | 
			
		||||
  Payment,
 | 
			
		||||
  Clawback,
 | 
			
		||||
} from '../../../src'
 | 
			
		||||
import serverUrl from '../serverUrl'
 | 
			
		||||
import {
 | 
			
		||||
  setupClient,
 | 
			
		||||
  teardownClient,
 | 
			
		||||
  type XrplIntegrationTestContext,
 | 
			
		||||
} from '../setup'
 | 
			
		||||
import { generateFundedWallet, testTransaction } from '../utils'
 | 
			
		||||
 | 
			
		||||
// how long before each test case times out
 | 
			
		||||
const TIMEOUT = 20000
 | 
			
		||||
 | 
			
		||||
describe('Clawback', function () {
 | 
			
		||||
  let testContext: XrplIntegrationTestContext
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    testContext = await setupClient(serverUrl)
 | 
			
		||||
  })
 | 
			
		||||
  afterEach(async () => teardownClient(testContext))
 | 
			
		||||
 | 
			
		||||
  it(
 | 
			
		||||
    'base',
 | 
			
		||||
    async () => {
 | 
			
		||||
      const wallet2 = await generateFundedWallet(testContext.client)
 | 
			
		||||
 | 
			
		||||
      const setupAccountSetTx: AccountSet = {
 | 
			
		||||
        TransactionType: 'AccountSet',
 | 
			
		||||
        Account: testContext.wallet.classicAddress,
 | 
			
		||||
        SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
 | 
			
		||||
      }
 | 
			
		||||
      await testTransaction(
 | 
			
		||||
        testContext.client,
 | 
			
		||||
        setupAccountSetTx,
 | 
			
		||||
        testContext.wallet,
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      const setupTrustSetTx: TrustSet = {
 | 
			
		||||
        TransactionType: 'TrustSet',
 | 
			
		||||
        Account: wallet2.classicAddress,
 | 
			
		||||
        LimitAmount: {
 | 
			
		||||
          currency: 'USD',
 | 
			
		||||
          issuer: testContext.wallet.classicAddress,
 | 
			
		||||
          value: '1000',
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      await testTransaction(testContext.client, setupTrustSetTx, wallet2)
 | 
			
		||||
 | 
			
		||||
      const setupPaymentTx: Payment = {
 | 
			
		||||
        TransactionType: 'Payment',
 | 
			
		||||
        Account: testContext.wallet.classicAddress,
 | 
			
		||||
        Destination: wallet2.classicAddress,
 | 
			
		||||
        Amount: {
 | 
			
		||||
          currency: 'USD',
 | 
			
		||||
          issuer: testContext.wallet.classicAddress,
 | 
			
		||||
          value: '1000',
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      await testTransaction(
 | 
			
		||||
        testContext.client,
 | 
			
		||||
        setupPaymentTx,
 | 
			
		||||
        testContext.wallet,
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      // verify that line is created
 | 
			
		||||
      const objectsResponse = await testContext.client.request({
 | 
			
		||||
        command: 'account_objects',
 | 
			
		||||
        account: wallet2.classicAddress,
 | 
			
		||||
        type: 'state',
 | 
			
		||||
      })
 | 
			
		||||
      assert.lengthOf(
 | 
			
		||||
        objectsResponse.result.account_objects,
 | 
			
		||||
        1,
 | 
			
		||||
        'Should be exactly one line on the ledger',
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      // actual test - clawback
 | 
			
		||||
      const tx: Clawback = {
 | 
			
		||||
        TransactionType: 'Clawback',
 | 
			
		||||
        Account: testContext.wallet.classicAddress,
 | 
			
		||||
        Amount: {
 | 
			
		||||
          currency: 'USD',
 | 
			
		||||
          issuer: wallet2.classicAddress,
 | 
			
		||||
          value: '500',
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      await testTransaction(testContext.client, tx, testContext.wallet)
 | 
			
		||||
 | 
			
		||||
      // verify amount clawed back
 | 
			
		||||
      const linesResponse = await testContext.client.request({
 | 
			
		||||
        command: 'account_lines',
 | 
			
		||||
        account: wallet2.classicAddress,
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      assert.lengthOf(
 | 
			
		||||
        linesResponse.result.lines,
 | 
			
		||||
        1,
 | 
			
		||||
        'Should be exactly one line on the ledger',
 | 
			
		||||
      )
 | 
			
		||||
      assert.equal(
 | 
			
		||||
        '500',
 | 
			
		||||
        linesResponse.result.lines[0].balance,
 | 
			
		||||
        `Holder balance incorrect after Clawback`,
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    TIMEOUT,
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										81
									
								
								packages/xahau/test/models/clawback.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								packages/xahau/test/models/clawback.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
import { assert } from 'chai'
 | 
			
		||||
 | 
			
		||||
import { validate, ValidationError } from '../../src'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clawback Transaction Verification Testing.
 | 
			
		||||
 *
 | 
			
		||||
 * Providing runtime verification testing for each specific transaction type.
 | 
			
		||||
 */
 | 
			
		||||
describe('Clawback', function () {
 | 
			
		||||
  it(`verifies valid Clawback`, function () {
 | 
			
		||||
    const validClawback = {
 | 
			
		||||
      TransactionType: 'Clawback',
 | 
			
		||||
      Amount: {
 | 
			
		||||
        currency: 'DSH',
 | 
			
		||||
        issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
 | 
			
		||||
        value: '43.11584856965009',
 | 
			
		||||
      },
 | 
			
		||||
      Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
    } as any
 | 
			
		||||
 | 
			
		||||
    assert.doesNotThrow(() => validate(validClawback))
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it(`throws w/ missing Amount`, function () {
 | 
			
		||||
    const missingAmount = {
 | 
			
		||||
      TransactionType: 'Clawback',
 | 
			
		||||
      Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
    } as any
 | 
			
		||||
 | 
			
		||||
    assert.throws(
 | 
			
		||||
      () => validate(missingAmount),
 | 
			
		||||
      ValidationError,
 | 
			
		||||
      'Clawback: missing field Amount',
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it(`throws w/ invalid Amount`, function () {
 | 
			
		||||
    const invalidAmount = {
 | 
			
		||||
      TransactionType: 'Clawback',
 | 
			
		||||
      Amount: 100000000,
 | 
			
		||||
      Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
    } as any
 | 
			
		||||
 | 
			
		||||
    assert.throws(
 | 
			
		||||
      () => validate(invalidAmount),
 | 
			
		||||
      ValidationError,
 | 
			
		||||
      'Clawback: invalid Amount',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const invalidStrAmount = {
 | 
			
		||||
      TransactionType: 'Clawback',
 | 
			
		||||
      Amount: '1234',
 | 
			
		||||
      Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
    } as any
 | 
			
		||||
 | 
			
		||||
    assert.throws(
 | 
			
		||||
      () => validate(invalidStrAmount),
 | 
			
		||||
      ValidationError,
 | 
			
		||||
      'Clawback: invalid Amount',
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it(`throws w/ invalid holder Account`, function () {
 | 
			
		||||
    const invalidAccount = {
 | 
			
		||||
      TransactionType: 'Clawback',
 | 
			
		||||
      Amount: {
 | 
			
		||||
        currency: 'DSH',
 | 
			
		||||
        issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
        value: '43.11584856965009',
 | 
			
		||||
      },
 | 
			
		||||
      Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
 | 
			
		||||
    } as any
 | 
			
		||||
 | 
			
		||||
    assert.throws(
 | 
			
		||||
      () => validate(invalidAccount),
 | 
			
		||||
      ValidationError,
 | 
			
		||||
      'Clawback: invalid holder Account',
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@@ -168,7 +168,8 @@ describe('Models Utils', function () {
 | 
			
		||||
        AccountRootFlags.lsfDisallowIncomingCheck |
 | 
			
		||||
        AccountRootFlags.lsfDisallowIncomingPayChan |
 | 
			
		||||
        AccountRootFlags.lsfDisallowIncomingTrustline |
 | 
			
		||||
        AccountRootFlags.lsfDisallowIncomingRemit
 | 
			
		||||
        AccountRootFlags.lsfDisallowIncomingRemit |
 | 
			
		||||
        AccountRootFlags.lsfAllowTrustLineClawback
 | 
			
		||||
 | 
			
		||||
      const parsed = parseAccountRootFlags(accountRootFlags)
 | 
			
		||||
 | 
			
		||||
@@ -186,7 +187,8 @@ describe('Models Utils', function () {
 | 
			
		||||
          parsed.lsfDisallowIncomingCheck &&
 | 
			
		||||
          parsed.lsfDisallowIncomingPayChan &&
 | 
			
		||||
          parsed.lsfDisallowIncomingTrustline &&
 | 
			
		||||
          parsed.lsfDisallowIncomingRemit,
 | 
			
		||||
          parsed.lsfDisallowIncomingRemit &&
 | 
			
		||||
          parsed.lsfAllowTrustLineClawback,
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@@ -207,6 +209,7 @@ describe('Models Utils', function () {
 | 
			
		||||
      assert.isUndefined(parsed.lsfDisallowIncomingPayChan)
 | 
			
		||||
      assert.isUndefined(parsed.lsfDisallowIncomingTrustline)
 | 
			
		||||
      assert.isUndefined(parsed.lsfDisallowIncomingRemit)
 | 
			
		||||
      assert.isUndefined(parsed.lsfAllowTrustLineClawback)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('parseTransactionFlags all enabled', function () {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user