Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add AMMClawback support #2893

Merged
merged 21 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci-config/rippled.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ fixXChainRewardRounding
fixPreviousTxnID
fixAMMv1_1
# 2.3.0 Amendments
AMMClawback
fixAMMv1_2
Credentials
NFTokenMintOffer
Expand Down
3 changes: 3 additions & 0 deletions packages/ripple-binary-codec/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Added
* Support for the AMMClawback amendment (XLS-73)

## 2.2.0 (2024-12-23)

### Added
Expand Down
1 change: 1 addition & 0 deletions packages/ripple-binary-codec/src/enums/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3073,6 +3073,7 @@
},
"TRANSACTION_TYPES": {
"AMMBid": 39,
"AMMClawback": 31,
"AMMCreate": 35,
"AMMDelete": 40,
"AMMDeposit": 36,
Expand Down
1 change: 1 addition & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased

### Added
* Support for the AMMClawback amendment (XLS-73)
* Adds utility function `convertTxFlagsToNumber`
* Implementation of XLS-80d PermissionedDomain feature.
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
Expand Down
120 changes: 120 additions & 0 deletions packages/xrpl/src/models/transactions/AMMClawback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ValidationError } from '../../errors'
import { Currency, IssuedCurrency, IssuedCurrencyAmount } from '../common'

import {
Account,
BaseTransaction,
GlobalFlags,
isAccount,
isAmount,
isCurrency,
validateBaseTransaction,
validateOptionalField,
validateRequiredField,
} from './common'

/**
* Enum representing values for AMMClawback Transaction Flags.
*
* @category Transaction Flags
*/
export enum AMMClawbackFlags {
tfClawTwoAssets = 0x00000001,
}

/**
* Map of flags to boolean values representing {@link AMMClawback} transaction
* flags.
*
* @category Transaction Flags
*/
export interface AMMClawbackFlagsInterface extends GlobalFlags {
tfClawTwoAssets?: boolean
}

/**
* Claw back tokens from a holder that has deposited your issued tokens into an AMM pool.
*
* Clawback is disabled by default. To use clawback, you must send an AccountSet transaction to enable the
* Allow Trust Line Clawback setting. An issuer with any existing tokens cannot enable clawback. You can
* only enable Allow Trust Line Clawback if you have a completely empty owner directory, meaning you must
* do so before you set up any trust lines, offers, escrows, payment channels, checks, or signer lists.
* After you enable clawback, it cannot reverted: the account permanently gains the ability to claw back
* issued assets on trust lines.
*/
export interface AMMClawback extends BaseTransaction {
TransactionType: 'AMMClawback'

/**
* The account holding the asset to be clawed back.
*/
Holder: Account

/**
* Specifies the asset that the issuer wants to claw back from the AMM pool.
* In JSON, this is an object with currency and issuer fields. The issuer field must match with Account.
*/
Asset: IssuedCurrency

/**
* Specifies the other asset in the AMM's pool. In JSON, this is an object with currency and
* issuer fields (omit issuer for XRP).
*/
Asset2: Currency

/**
* The maximum amount to claw back from the AMM account. The currency and issuer subfields should match
* the Asset subfields. If this field isn't specified, or the value subfield exceeds the holder's available
* tokens in the AMM, all of the holder's tokens will be clawed back.
*/
Amount?: IssuedCurrencyAmount
}

/**
* Verify the form and type of an AMMClawback at runtime.
*
* @param tx - An AMMClawback Transaction.
* @throws {ValidationError} When the transaction is malformed.
*/
export function validateAMMClawback(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)

validateRequiredField(tx, 'Holder', isAccount)

validateRequiredField(tx, 'Asset', isCurrency)

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
const asset = tx.Asset as IssuedCurrency
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
const amount = tx.Amount as IssuedCurrencyAmount

if (tx.Holder === asset.issuer) {
throw new ValidationError(
'AMMClawback: Holder and Asset.issuer must be distinct',
)
}

if (tx.Account !== asset.issuer) {
throw new ValidationError(
'AMMClawback: Account must be the same as Asset.issuer',
)
}

validateRequiredField(tx, 'Asset2', isCurrency)

validateOptionalField(tx, 'Amount', isAmount)

if (tx.Amount != null) {
if (amount.currency !== asset.currency) {
throw new ValidationError(
'AMMClawback: Amount.currency must match Asset.currency',
)
}

if (amount.issuer !== asset.issuer) {
throw new ValidationError(
'AMMClawback: Amount.issuer must match Amount.issuer',
)
}
}
}
7 changes: 6 additions & 1 deletion packages/xrpl/src/models/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ export {
} from './accountSet'
export { AccountDelete } from './accountDelete'
export { AMMBid } from './AMMBid'
export {
AMMClawbackFlags,
AMMClawbackFlagsInterface,
AMMClawback,
} from './AMMClawback'
export { AMMCreate } from './AMMCreate'
export { AMMDelete } from './AMMDelete'
export {
AMMDepositFlags,
AMMDepositFlagsInterface,
AMMDeposit,
} from './AMMDeposit'
export { AMMCreate } from './AMMCreate'
export { AMMVote } from './AMMVote'
export {
AMMWithdrawFlags,
Expand Down
6 changes: 6 additions & 0 deletions packages/xrpl/src/models/transactions/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { convertTxFlagsToNumber } from '../utils/flags'
import { AccountDelete, validateAccountDelete } from './accountDelete'
import { AccountSet, validateAccountSet } from './accountSet'
import { AMMBid, validateAMMBid } from './AMMBid'
import { AMMClawback, validateAMMClawback } from './AMMClawback'
import { AMMCreate, validateAMMCreate } from './AMMCreate'
import { AMMDelete, validateAMMDelete } from './AMMDelete'
import { AMMDeposit, validateAMMDeposit } from './AMMDeposit'
Expand Down Expand Up @@ -123,6 +124,7 @@ import {
*/
export type SubmittableTransaction =
| AMMBid
| AMMClawback
| AMMCreate
| AMMDelete
| AMMDeposit
Expand Down Expand Up @@ -273,6 +275,10 @@ export function validate(transaction: Record<string, unknown>): void {
validateAMMBid(tx)
break

case 'AMMClawback':
validateAMMClawback(tx)
break

case 'AMMCreate':
validateAMMCreate(tx)
break
Expand Down
2 changes: 2 additions & 0 deletions packages/xrpl/src/models/utils/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AccountRootFlags,
} from '../ledger/AccountRoot'
import { AccountSetTfFlags } from '../transactions/accountSet'
import { AMMClawbackFlags } from '../transactions/AMMClawback'
import { AMMDepositFlags } from '../transactions/AMMDeposit'
import { AMMWithdrawFlags } from '../transactions/AMMWithdraw'
import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize'
Expand Down Expand Up @@ -47,6 +48,7 @@ export function parseAccountRootFlags(

const txToFlag = {
AccountSet: AccountSetTfFlags,
AMMClawback: AMMClawbackFlags,
AMMDeposit: AMMDepositFlags,
AMMWithdraw: AMMWithdrawFlags,
MPTokenAuthorize: MPTokenAuthorizeFlags,
Expand Down
57 changes: 57 additions & 0 deletions packages/xrpl/test/integration/transactions/ammClawback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AMMClawback, AMMDeposit, AMMDepositFlags, XRP } from 'xrpl'

import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { createAMMPool, testTransaction } from '../utils'

describe('AMMClawback', function () {
let testContext: XrplIntegrationTestContext

beforeAll(async () => {
testContext = await setupClient(serverUrl)
})
afterAll(async () => teardownClient(testContext))

it('base', async function () {
const ammPool = await createAMMPool(testContext.client, true)
const { issuerWallet } = ammPool
const holderWallet = ammPool.lpWallet

const asset = {
currency: 'USD',
issuer: issuerWallet.classicAddress,
}
const asset2 = {
currency: 'XRP',
} as XRP

const ammDepositTx: AMMDeposit = {
TransactionType: 'AMMDeposit',
Account: holderWallet.classicAddress,
Asset: asset,
Asset2: asset2,
Amount: {
currency: 'USD',
issuer: issuerWallet.address,
value: '10',
},
Flags: AMMDepositFlags.tfSingleAsset,
}

await testTransaction(testContext.client, ammDepositTx, holderWallet)

const ammClawback: AMMClawback = {
TransactionType: 'AMMClawback',
Account: issuerWallet.address,
Holder: holderWallet.address,
Asset: asset,
Asset2: asset2,
}

await testTransaction(testContext.client, ammClawback, issuerWallet)
})
})
15 changes: 14 additions & 1 deletion packages/xrpl/test/integration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,10 @@ export async function getIOUBalance(
return (await client.request(request)).result.lines[0].balance
}

export async function createAMMPool(client: Client): Promise<{
export async function createAMMPool(
client: Client,
enableAMMClawback = false,
): Promise<{
issuerWallet: Wallet
lpWallet: Wallet
asset: Currency
Expand All @@ -391,6 +394,16 @@ export async function createAMMPool(client: Client): Promise<{

await testTransaction(client, accountSetTx, issuerWallet)

if (enableAMMClawback) {
const accountSetTx2: AccountSet = {
TransactionType: 'AccountSet',
Account: issuerWallet.classicAddress,
SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
}

await testTransaction(client, accountSetTx2, issuerWallet)
}

const trustSetTx: TrustSet = {
TransactionType: 'TrustSet',
Flags: TrustSetFlags.tfClearNoRipple,
Expand Down
Loading
Loading