mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 13:05:49 +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:
@@ -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