Skip to content

Commit

Permalink
Use TxSetNoRipple for all XRPL TrustSet transactions. (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzmitryhil authored Mar 13, 2024
1 parent 2bac6bb commit 218cec6
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 0 deletions.
1 change: 1 addition & 0 deletions integration-tests/contract/token_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func TestRegisterAndUpdateCoreumToken(t *testing.T) {
},
TxBase: rippledata.TxBase{
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
}
require.NoError(t, chains.XRPL.AutoFillSignAndSubmitTx(ctx, t, &trustSetTx, recipientAcc))
Expand Down
195 changes: 195 additions & 0 deletions integration-tests/processes/sending_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package processes_test

import (
"context"
"encoding/hex"
"fmt"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -1960,3 +1962,196 @@ func TestSendFromCoreumToXRPLProhibitedAddresses(t *testing.T) {
)
require.True(t, coreum.IsProhibitedAddressError(err), err)
}

func TestSendXRPLOriginatedTokensWithRippling(t *testing.T) {
t.Parallel()

ctx, chains := integrationtests.NewTestingContext(t)

envCfg := DefaultRunnerEnvConfig()
runnerEnv := NewRunnerEnv(ctx, t, envCfg, chains)
runnerEnv.StartAllRunnerProcesses()
runnerEnv.AllocateTickets(ctx, t, uint32(200))

coreumRecipient := chains.Coreum.GenAccount()

// generate 2 issuers
xrplIssuer1Address := chains.XRPL.GenAccount(ctx, t, 1)
xrplIssuer2Address := chains.XRPL.GenAccount(ctx, t, 1)

// generate 2 holders
xrplHolder1Address := chains.XRPL.GenAccount(ctx, t, 1)
xrplHolder2Address := chains.XRPL.GenAccount(ctx, t, 1)

// enable to be able to send to any address
runnerEnv.EnableXRPLAccountRippling(ctx, t, xrplIssuer1Address)
runnerEnv.EnableXRPLAccountRippling(ctx, t, xrplIssuer2Address)

xrplCurrency, err := rippledata.NewCurrency("FOO")
require.NoError(t, err)

// register 2 tokens with the same currency, but different issuer
registeredIssuer1XRPLToken := runnerEnv.RegisterXRPLOriginatedToken(
ctx,
t,
xrplIssuer1Address,
xrplCurrency,
int32(6),
integrationtests.ConvertStringWithDecimalsToSDKInt(t, "1", 30),
sdkmath.ZeroInt(),
)
registeredIssuer2XRPLToken := runnerEnv.RegisterXRPLOriginatedToken(
ctx,
t,
xrplIssuer2Address,
xrplCurrency,
int32(6),
integrationtests.ConvertStringWithDecimalsToSDKInt(t, "1", 30),
sdkmath.ZeroInt(),
)

// lock 100 tokens of issuer 1 in the bridge XRPL account
valueSentToCoreum, err := rippledata.NewValue("100", false)
require.NoError(t, err)
issuer1AmountToSendToCoreum := rippledata.Amount{
Value: valueSentToCoreum,
Currency: xrplCurrency,
Issuer: xrplIssuer1Address,
}
runnerEnv.SendFromXRPLToCoreum(ctx, t, xrplIssuer1Address.String(), issuer1AmountToSendToCoreum, coreumRecipient)
runnerEnv.AwaitCoreumBalance(
ctx,
t,
coreumRecipient,
sdk.NewCoin(
registeredIssuer1XRPLToken.CoreumDenom,
integrationtests.ConvertStringWithDecimalsToSDKInt(
t,
valueSentToCoreum.String(),
xrpl.XRPLIssuedTokenDecimals,
),
),
)

// lock 100 tokens of issuer 2 in the bridge XRPL account
issuer2AmountToSendToCoreum := rippledata.Amount{
Value: valueSentToCoreum,
Currency: xrplCurrency,
Issuer: xrplIssuer2Address,
}
runnerEnv.SendFromXRPLToCoreum(ctx, t, xrplIssuer2Address.String(), issuer2AmountToSendToCoreum, coreumRecipient)
runnerEnv.AwaitCoreumBalance(
ctx,
t,
coreumRecipient,
sdk.NewCoin(
registeredIssuer2XRPLToken.CoreumDenom,
integrationtests.ConvertStringWithDecimalsToSDKInt(
t,
valueSentToCoreum.String(),
xrpl.XRPLIssuedTokenDecimals,
),
),
)

// now the XRPL bridge account address holds 100 currency/issuer1 and 100 currency/issuer2
bridgeXRPLAddressIssuer1CurrencyBalances := getBalanceByIssuerAndCurrency(
ctx, t, runnerEnv, runnerEnv.BridgeXRPLAddress, xrplIssuer1Address, xrplCurrency,
)
require.Equal(t, valueSentToCoreum.String(), bridgeXRPLAddressIssuer1CurrencyBalances.Value.String())

bridgeXRPLAddressIssuer2CurrencyBalances := getBalanceByIssuerAndCurrency(
ctx, t, runnerEnv, runnerEnv.BridgeXRPLAddress, xrplIssuer2Address, xrplCurrency,
)
require.Equal(t, valueSentToCoreum.String(), bridgeXRPLAddressIssuer2CurrencyBalances.Value.String())

// fund holders
valueToFundHolder, err := rippledata.NewValue("20", false)

runnerEnv.SendXRPLMaxTrustSetTx(ctx, t, xrplHolder1Address, xrplIssuer1Address, xrplCurrency)
require.NoError(t, err)
issuer1AmountToFundHolder1 := rippledata.Amount{
Value: valueToFundHolder,
Currency: xrplCurrency,
Issuer: xrplIssuer1Address,
}
runnerEnv.SendXRPLPaymentTx(
ctx,
t,
xrplIssuer1Address,
xrplHolder1Address,
issuer1AmountToFundHolder1,
rippledata.Memo{},
)

runnerEnv.SendXRPLMaxTrustSetTx(ctx, t, xrplHolder2Address, xrplIssuer2Address, xrplCurrency)
issuer2AmountToFundHolder1 := rippledata.Amount{
Value: valueToFundHolder,
Currency: xrplCurrency,
Issuer: xrplIssuer2Address,
}
runnerEnv.SendXRPLPaymentTx(
ctx,
t,
xrplIssuer2Address,
xrplHolder2Address,
issuer2AmountToFundHolder1,
rippledata.Memo{},
)

// now the holder1 and holder2 balances are 20currency/issuer1 and 20currency/issuer2 correspondingly
xrplHolder1Issuer1CurrencyBalances := getBalanceByIssuerAndCurrency(
ctx, t, runnerEnv, xrplHolder1Address, xrplIssuer1Address, xrplCurrency,
)
require.Equal(t, valueToFundHolder.String(), xrplHolder1Issuer1CurrencyBalances.Value.String())

xrplHolder2Issuer2Balances := getBalanceByIssuerAndCurrency(
ctx, t, runnerEnv, xrplHolder2Address, xrplIssuer2Address, xrplCurrency,
)
require.Equal(t, valueToFundHolder.String(), xrplHolder2Issuer2Balances.Value.String())

// now we have a state when the XRPL bridge contract address could allow swap of the issuer1 currency to issuer2
// currency without the TxSetNoRipple flag enabled on the trust set. But since we use TxSetNoRipple such situation
// not expected.

// try to find a path to send payment from holder2 to holder1 with the bridge in PATH
valueToSendToHolder2, err := rippledata.NewValue("10", false)
require.NoError(t, err)
amountSendToHolder2 := rippledata.Amount{
Value: valueToSendToHolder2,
Currency: xrplCurrency,
Issuer: xrplIssuer1Address,
}
paths, err := runnerEnv.Chains.XRPL.RPCClient().RipplePathFind(
ctx,
xrplHolder2Address,
&[]rippledata.Currency{
xrplCurrency,
},
xrplHolder1Address,
// the issuer here is the issuer which will be received
amountSendToHolder2,
)
require.NoError(t, err)
// no alternatives found, meas we can't execute the tx through the bridge XRPL address.
require.Empty(t, paths.Alternatives)
}

func getBalanceByIssuerAndCurrency(
ctx context.Context,
t *testing.T,
runnerEnv *RunnerEnv,
acc, issuer rippledata.Account,
currency rippledata.Currency,
) rippledata.Amount {
balances, err := runnerEnv.Chains.XRPL.RPCClient().GetXRPLBalances(ctx, acc)
require.NoError(t, err)
keyName := fmt.Sprintf(
"%s/%s", xrpl.ConvertCurrencyToString(currency), issuer,
)
return lo.FindOrElse(balances, rippledata.Amount{}, func(item rippledata.Amount) bool {
return keyName == fmt.Sprintf(
"%s/%s", xrpl.ConvertCurrencyToString(currency), item.Issuer.String(),
)
})
}
2 changes: 2 additions & 0 deletions integration-tests/xrpl/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func TestXRPAndIssuedTokensPayment(t *testing.T) {
},
TxBase: rippledata.TxBase{
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
}
require.NoError(t, chains.XRPL.AutoFillSignAndSubmitTx(ctx, t, &fooCurrencyTrustSetTx, recipientAcc))
Expand Down Expand Up @@ -843,6 +844,7 @@ func TestXRPLHighLowAmountsPayments(t *testing.T) {
},
TxBase: rippledata.TxBase{
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
}
require.NoError(t, chains.XRPL.AutoFillSignAndSubmitTx(ctx, t, &fooCurrencyTrustSetTx, acc))
Expand Down
1 change: 1 addition & 0 deletions relayer/client/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ func (b *BridgeClient) SetXRPLTrustSet(
LimitAmount: limitAmount,
TxBase: rippledata.TxBase{
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
}

Expand Down
1 change: 1 addition & 0 deletions relayer/processes/coreum_to_xrpl_operation_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func BuildTrustSetTxForMultiSigning(
TxBase: rippledata.TxBase{
Account: bridgeXRPLAddress,
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
LimitAmount: value,
}
Expand Down
3 changes: 3 additions & 0 deletions relayer/processes/xrpl_to_coreum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ func TestXRPLToCoreumProcess_Start(t *testing.T) {
Transaction: &rippledata.TrustSet{
TxBase: rippledata.TxBase{
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
},
}
Expand Down Expand Up @@ -411,6 +412,7 @@ func TestXRPLToCoreumProcess_Start(t *testing.T) {
TxBase: rippledata.TxBase{
Account: bridgeXRPLAddress,
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
LimitAmount: xrplOriginatedTokenXRPLAmount,
TicketSequence: lo.ToPtr(uint32(11)),
Expand Down Expand Up @@ -451,6 +453,7 @@ func TestXRPLToCoreumProcess_Start(t *testing.T) {
TxBase: rippledata.TxBase{
Account: bridgeXRPLAddress,
TransactionType: rippledata.TRUST_SET,
Flags: lo.ToPtr(rippledata.TxSetNoRipple),
},
LimitAmount: xrplOriginatedTokenXRPLAmount,
TicketSequence: lo.ToPtr(uint32(11)),
Expand Down
61 changes: 61 additions & 0 deletions relayer/xrpl/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/pkg/errors"
rippledata "github.com/rubblelabs/ripple/data"
"github.com/samber/lo"
"go.uber.org/zap"

"github.com/CoreumFoundation/coreum-tools/pkg/retry"
Expand Down Expand Up @@ -167,6 +168,30 @@ type ServerStateResult struct {
Status string `json:"status"`
}

// SrcCurrency is source currency for the pathfinding.
type SrcCurrency struct {
Currency rippledata.Currency `json:"currency"`
}

// RipplePathFindRequest is ripple_path_find request.
type RipplePathFindRequest struct {
SrcAccount rippledata.Account `json:"source_account"`
SrcCurrencies *[]SrcCurrency `json:"source_currencies,omitempty"`
DestAccount rippledata.Account `json:"destination_account"`
DestAmount rippledata.Amount `json:"destination_amount"`
}

// RipplePathFindResult is ripple_path_find result.
type RipplePathFindResult struct {
Alternatives []struct {
SrcAmount rippledata.Amount `json:"source_amount"`
PathsComputed rippledata.PathSet `json:"paths_computed,omitempty"`
PathsCanonical rippledata.PathSet `json:"paths_canonical,omitempty"`
}
DestAccount rippledata.Account `json:"destination_account"`
DestCurrencies []rippledata.Currency `json:"destination_currencies"`
}

// ******************** RPC transport objects ********************

type rpcRequest struct {
Expand Down Expand Up @@ -368,6 +393,42 @@ func (c *RPCClient) ServerState(ctx context.Context) (ServerStateResult, error)
return result, nil
}

// RipplePathFind returns the found ripple paths.
func (c *RPCClient) RipplePathFind(
ctx context.Context,
srcAccount rippledata.Account,
srcCurrencies *[]rippledata.Currency,
destAccount rippledata.Account,
destAmount rippledata.Amount,
) (RipplePathFindResult, error) {
var paramsSrcCurrencies *[]SrcCurrency
if srcCurrencies != nil {
paramsSrcCurrencies = lo.ToPtr(
lo.Map(
*srcCurrencies,
func(currency rippledata.Currency, _ int) SrcCurrency {
return SrcCurrency{
Currency: currency,
}
}),
)
}

params := RipplePathFindRequest{
SrcAccount: srcAccount,
SrcCurrencies: paramsSrcCurrencies,
DestAccount: destAccount,
DestAmount: destAmount,
}

var result RipplePathFindResult
if err := c.callRPC(ctx, "ripple_path_find", params, &result); err != nil {
return RipplePathFindResult{}, err
}

return result, nil
}

func (c *RPCClient) callRPC(ctx context.Context, method string, params, result any) error {
request := rpcRequest{
Method: method,
Expand Down

0 comments on commit 218cec6

Please sign in to comment.