Skip to content

Commit

Permalink
Relayer: Sending of the coreum originated token from the coreum to XR…
Browse files Browse the repository at this point in the history
…PL. (#50)

Relayer: Sending of the coreum originated token from the coreum to XRPL.

* Add contract client integration tests
* Implement relayer part for the sending
  • Loading branch information
dzmitryhil authored Nov 24, 2023
1 parent 7701c01 commit 52558e7
Show file tree
Hide file tree
Showing 24 changed files with 1,167 additions and 391 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/relayer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
linter-cache: false
docker-cache: false
- ci_step: "integration tests"
command: "make build-contract && make rebuild-dev-env && make restart-dev-env && make test-integration"
command: "make build-contract && make build-dev-env && make restart-dev-env && make test-integration"
go-cache: true
wasm-cache: true
linter-cache: false
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ test-contract:
restart-dev-env:
crust znet remove && crust znet start --profiles=1cored,xrpl --timeout-commit 0.5s

.PHONY: rebuild-dev-env
rebuild-dev-env:
.PHONY: build-dev-env
build-dev-env:
crust build/crust images/cored

.PHONY: smoke
Expand Down
479 changes: 427 additions & 52 deletions integration-tests/coreum/contract_client_test.go

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion integration-tests/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func init() {
// parse additional flags
flag.Parse()

zapDevLogger, err := zap.NewDevelopment()
zapDevConfig := zap.NewDevelopmentConfig()
zapDevConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
zapDevLogger, err := zapDevConfig.Build()
if err != nil {
panic(errors.WithStack(err))
}
Expand Down
22 changes: 10 additions & 12 deletions integration-tests/processes/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ func NewRunnerEnv(ctx context.Context, t *testing.T, cfg RunnerEnvConfig, chains
runners = append(
runners,
createDevRunner(
ctx,
t,
chains,
bridgeXRPLAddress,
relayerXRPLAddresses[i],
contractClient.GetContractAddress(),
relayerCoreumAddresses[i],
Expand All @@ -125,9 +125,9 @@ func NewRunnerEnv(ctx context.Context, t *testing.T, cfg RunnerEnvConfig, chains
runners = append(
runners,
createDevRunner(
ctx,
t,
chains,
bridgeXRPLAddress,
maliciousXRPLAddress,
contractClient.GetContractAddress(),
relayerCoreumAddresses[i],
Expand Down Expand Up @@ -269,12 +269,11 @@ func (r *RunnerEnv) RegisterXRPLOriginatedToken(
require.NoError(t, err)
// await for the trust set
r.AwaitNoPendingOperations(ctx, t)
registeredXRPLToken, err := r.ContractClient.GetXRPLToken(ctx, issuer.String(), xrpl.ConvertCurrencyToString(currency))
registeredXRPLToken, err := r.ContractClient.GetXRPLTokenByIssuerAndCurrency(ctx, issuer.String(), xrpl.ConvertCurrencyToString(currency))
require.NoError(t, err)
require.NotNil(t, registeredXRPLToken)
require.Equal(t, coreum.TokenStateEnabled, registeredXRPLToken.State)

return *registeredXRPLToken
return registeredXRPLToken
}

// RequireNoErrors check whether the runner err received runner errors.
Expand Down Expand Up @@ -347,7 +346,7 @@ func (r *RunnerEnv) SendXRPLMaxTrustSetTx(
issuer rippledata.Account,
currency rippledata.Currency,
) {
value, err := rippledata.NewValue("1000000000000", false)
value, err := rippledata.NewValue("1e80", false)
require.NoError(t, err)
trustSetTx := rippledata.TrustSet{
LimitAmount: rippledata.Amount{
Expand Down Expand Up @@ -439,12 +438,12 @@ func genBridgeXRPLAccountWithRelayers(
}

func createDevRunner(
ctx context.Context,
t *testing.T,
chains integrationtests.Chains,
bridgeXRPLAddress rippledata.Account,
xrplRelayerAcc rippledata.Account,
contractAddress sdk.AccAddress,
coreumRelayerAddress sdk.AccAddress,
relayerCoreumAddress sdk.AccAddress,
) *runner.Runner {
t.Helper()

Expand All @@ -458,7 +457,7 @@ func createDevRunner(

// reimport coreum key
coreumKr := chains.Coreum.ClientContext.Keyring()
keyInfo, err := coreumKr.KeyByAddress(coreumRelayerAddress)
keyInfo, err := coreumKr.KeyByAddress(relayerCoreumAddress)
require.NoError(t, err)
pass := uuid.NewString()
armor, err := coreumKr.ExportPrivKeyArmor(keyInfo.Name, pass)
Expand All @@ -474,9 +473,8 @@ func createDevRunner(
require.NoError(t, kr.ImportPrivKey(relayerXRPLKeyName, armor, pass))

relayerRunnerCfg := runner.DefaultConfig()
relayerRunnerCfg.LoggingConfig.Level = "debug"
relayerRunnerCfg.LoggingConfig.Level = "info"

relayerRunnerCfg.XRPL.BridgeAccount = bridgeXRPLAddress.String()
relayerRunnerCfg.XRPL.MultiSignerKeyName = relayerXRPLKeyName
relayerRunnerCfg.XRPL.RPC.URL = chains.XRPL.Config().RPCAddress
// make the scanner fast
Expand All @@ -493,7 +491,7 @@ func createDevRunner(
// make operation fetcher fast
relayerRunnerCfg.Processes.XRPLTxSubmitter.RepeatDelay = 500 * time.Millisecond

relayerRunner, err := runner.NewRunner(relayerRunnerCfg, kr)
relayerRunner, err := runner.NewRunner(ctx, relayerRunnerCfg, kr)
require.NoError(t, err)
return relayerRunner
}
92 changes: 92 additions & 0 deletions integration-tests/processes/send_from_coreum_to_xrpl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
rippledata "github.com/rubblelabs/ripple/data"
"github.com/stretchr/testify/require"

"github.com/CoreumFoundation/coreum/v3/pkg/client"
coreumintegration "github.com/CoreumFoundation/coreum/v3/testutil/integration"
assetfttypes "github.com/CoreumFoundation/coreum/v3/x/asset/ft/types"
integrationtests "github.com/CoreumFoundation/coreumbridge-xrpl/integration-tests"
"github.com/CoreumFoundation/coreumbridge-xrpl/relayer/coreum"
"github.com/CoreumFoundation/coreumbridge-xrpl/relayer/xrpl"
Expand Down Expand Up @@ -203,3 +205,93 @@ func TestSendFromXRPLToCoreumWithTicketsReallocation(t *testing.T) {
balance := runnerEnv.Chains.XRPL.GetAccountBalance(ctx, t, xrplRecipientAddress, xrplIssuerAddress, registeredXRPLCurrency)
require.Equal(t, totalSent.Quo(sdkmath.NewIntWithDecimal(1, XRPLTokenDecimals)).String(), balance.Value.String())
}

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

ctx, chains := integrationtests.NewTestingContext(t)

xrplRecipientAddress := chains.XRPL.GenAccount(ctx, t, 0)
t.Logf("XRPL recipient address: %s", xrplRecipientAddress)

coreumSenderAddress := chains.Coreum.GenAccount()
issueFee := chains.Coreum.QueryAssetFTParams(ctx, t).IssueFee
chains.Coreum.FundAccountWithOptions(ctx, t, coreumSenderAddress, coreumintegration.BalancesOptions{
Amount: issueFee.Amount.Add(sdkmath.NewInt(10_000_000)),
})

// issue asset ft and register it
sendingPrecision := int32(2)
tokenDecimals := uint32(4)
maxHoldingAmount, ok := sdk.NewIntFromString("10000000000000000")
require.True(t, ok)
issueMsg := &assetfttypes.MsgIssue{
Issuer: coreumSenderAddress.String(),
Symbol: "denom",
Subunit: "denom",
Precision: tokenDecimals, // token decimals in terms of the contract
InitialAmount: maxHoldingAmount,
}
_, err := client.BroadcastTx(
ctx,
chains.Coreum.ClientContext.WithFromAddress(coreumSenderAddress),
chains.Coreum.TxFactory().WithSimulateAndExecute(true),
issueMsg,
)
require.NoError(t, err)

envCfg := DefaultRunnerEnvConfig()
runnerEnv := NewRunnerEnv(ctx, t, envCfg, chains)

bridgeXRPLAccountInfo, err := chains.XRPL.RPCClient().AccountInfo(ctx, runnerEnv.bridgeXRPLAddress)
require.NoError(t, err)

// recover tickets so we can register tokens
numberOfTicketsToAllocate := uint32(200)
chains.XRPL.FundAccountForTicketAllocation(ctx, t, runnerEnv.bridgeXRPLAddress, numberOfTicketsToAllocate)
_, err = runnerEnv.ContractClient.RecoverTickets(ctx, runnerEnv.ContractOwner, *bridgeXRPLAccountInfo.AccountData.Sequence, &numberOfTicketsToAllocate)
require.NoError(t, err)

// start relayers
runnerEnv.StartAllRunnerProcesses(ctx, t)
runnerEnv.AwaitNoPendingOperations(ctx, t)
availableTickets, err := runnerEnv.ContractClient.GetAvailableTickets(ctx)
require.NoError(t, err)
require.Len(t, availableTickets, int(numberOfTicketsToAllocate))

// register Coreum originated token
require.NoError(t, err)
denom := assetfttypes.BuildDenom(issueMsg.Subunit, coreumSenderAddress)
_, err = runnerEnv.ContractClient.RegisterCoreumToken(ctx, runnerEnv.ContractOwner, denom, tokenDecimals, sendingPrecision, maxHoldingAmount)
require.NoError(t, err)
registeredCoreumOriginatedToken, err := runnerEnv.ContractClient.GetCoreumTokenByDenom(ctx, denom)
require.NoError(t, err)

// send TrustSet to be able to receive coins from the bridge
xrplCurrency, err := rippledata.NewCurrency(registeredCoreumOriginatedToken.XRPLCurrency)
require.NoError(t, err)
runnerEnv.SendXRPLMaxTrustSetTx(ctx, t, xrplRecipientAddress, runnerEnv.bridgeXRPLAddress, xrplCurrency)

// equal to 11.1111 on XRPL, but with the sending prec 2 we expect 11.11 to be received
amountToSend1 := sdkmath.NewInt(111111)
// TODO(dzmitryhil) update assertion once we add the final tx revert/recovery
_, err = runnerEnv.ContractClient.SendToXRPL(ctx, coreumSenderAddress, xrplRecipientAddress.String(), sdk.NewCoin(registeredCoreumOriginatedToken.Denom, amountToSend1))
require.NoError(t, err)

runnerEnv.AwaitNoPendingOperations(ctx, t)

// check the XRPL recipient balance
balance := runnerEnv.Chains.XRPL.GetAccountBalance(ctx, t, xrplRecipientAddress, runnerEnv.bridgeXRPLAddress, xrplCurrency)
require.Equal(t, "11.11", balance.Value.String())

amountToSend2 := maxHoldingAmount.QuoRaw(2)
require.NoError(t, err)
_, err = runnerEnv.ContractClient.SendToXRPL(ctx, coreumSenderAddress, xrplRecipientAddress.String(), sdk.NewCoin(registeredCoreumOriginatedToken.Denom, amountToSend2))
require.NoError(t, err)

runnerEnv.AwaitNoPendingOperations(ctx, t)

// check the XRPL recipient balance
balance = runnerEnv.Chains.XRPL.GetAccountBalance(ctx, t, xrplRecipientAddress, runnerEnv.bridgeXRPLAddress, xrplCurrency)
require.Equal(t, "50000000001111e-2", balance.Value.String())
}
6 changes: 2 additions & 4 deletions integration-tests/processes/send_from_xrpl_to_coreum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,12 @@ func TestRegisterXRPLOriginatedTokensAndSendFromXRPLToCoreum(t *testing.T) {
// await for the trust set
runnerEnv.AwaitNoPendingOperations(ctx, t)

registeredXRPLToken, err := runnerEnv.ContractClient.GetXRPLToken(ctx, xrplIssuerAddress.String(), xrpl.ConvertCurrencyToString(registeredXRPLCurrency))
registeredXRPLToken, err := runnerEnv.ContractClient.GetXRPLTokenByIssuerAndCurrency(ctx, xrplIssuerAddress.String(), xrpl.ConvertCurrencyToString(registeredXRPLCurrency))
require.NoError(t, err)
require.NotNil(t, registeredXRPLToken)
require.Equal(t, coreum.TokenStateEnabled, registeredXRPLToken.State)

registeredXRPLHexCurrencyToken, err := runnerEnv.ContractClient.GetXRPLToken(ctx, xrplIssuerAddress.String(), xrpl.ConvertCurrencyToString(registeredXRPLHexCurrency))
registeredXRPLHexCurrencyToken, err := runnerEnv.ContractClient.GetXRPLTokenByIssuerAndCurrency(ctx, xrplIssuerAddress.String(), xrpl.ConvertCurrencyToString(registeredXRPLHexCurrency))
require.NoError(t, err)
require.NotNil(t, registeredXRPLHexCurrencyToken)
require.Equal(t, coreum.TokenStateEnabled, registeredXRPLHexCurrencyToken.State)

lowValue, err := rippledata.NewValue("1.00000111", false)
Expand Down
11 changes: 10 additions & 1 deletion integration-tests/xrpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,16 @@ func (c XRPLChain) SubmitTx(ctx context.Context, t *testing.T, tx rippledata.Tra

// GetAccountBalance returns account balance for the provided issuer and currency.
func (c XRPLChain) GetAccountBalance(ctx context.Context, t *testing.T, account, issuer rippledata.Account, currency rippledata.Currency) rippledata.Amount {
return c.GetAccountBalances(ctx, t, account)[fmt.Sprintf("%s/%s", currency.String(), issuer.String())]
balance, ok := c.GetAccountBalances(ctx, t, account)[fmt.Sprintf("%s/%s", currency.String(), issuer.String())]
if !ok {
// equal to zero
return rippledata.Amount{
Value: &rippledata.Value{},
Currency: currency,
Issuer: issuer,
}
}
return balance
}

// GetAccountBalances returns account balances.
Expand Down
60 changes: 48 additions & 12 deletions relayer/coreum/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,24 @@ type ContractOwnership struct {

// XRPLToken is XRPL token representation on coreum.
type XRPLToken struct {
Issuer string `json:"issuer"`
Currency string `json:"currency"`
CoreumDenom string `json:"coreum_denom"`
State TokenState `json:"state"`
Issuer string `json:"issuer"`
Currency string `json:"currency"`
CoreumDenom string `json:"coreum_denom"`
SendingPrecision int32 `json:"sending_precision"`
MaxHoldingAmount sdkmath.Int `json:"max_holding_amount"`
State TokenState `json:"state"`
}

// CoreumToken is coreum token registered on the contract.
//
//nolint:revive //kept for the better naming convention.
type CoreumToken struct {
Denom string `json:"denom"`
Decimals uint32 `json:"decimals"`
XRPLCurrency string `json:"xrpl_currency"`
Denom string `json:"denom"`
Decimals uint32 `json:"decimals"`
XRPLCurrency string `json:"xrpl_currency"`
SendingPrecision int32 `json:"sending_precision"`
MaxHoldingAmount sdkmath.Int `json:"max_holding_amount"`
State TokenState `json:"state"`
}

// XRPLToCoreumTransferEvidence is evidence with values represented sending from XRPL to coreum.
Expand Down Expand Up @@ -717,19 +722,19 @@ func (c *ContractClient) GetContractOwnership(ctx context.Context) (ContractOwne
return response, nil
}

// GetXRPLToken returns an XRPL registered token or nil.
func (c *ContractClient) GetXRPLToken(ctx context.Context, issuer, currency string) (*XRPLToken, error) {
// GetXRPLTokenByIssuerAndCurrency returns a XRPL registered token by issuer and currency or error.
func (c *ContractClient) GetXRPLTokenByIssuerAndCurrency(ctx context.Context, issuer, currency string) (XRPLToken, error) {
tokens, err := c.GetXRPLTokens(ctx)
if err != nil {
return nil, err
return XRPLToken{}, err
}
for _, token := range tokens {
if token.Issuer == issuer && token.Currency == currency {
return &token, nil
return token, nil
}
}

return nil, nil //nolint:nilnil // if token not found we return nil instead of an error
return XRPLToken{}, errors.Errorf("token not found in the registered tokens list, issuer:%s, currency:%s", issuer, currency)
}

// GetXRPLTokens returns a list of all XRPL tokens.
Expand All @@ -751,6 +756,37 @@ func (c *ContractClient) GetXRPLTokens(ctx context.Context) ([]XRPLToken, error)
return tokens, nil
}

// GetCoreumTokenByDenom returns a coreum registered token or nil by the provided denom.
func (c *ContractClient) GetCoreumTokenByDenom(ctx context.Context, denom string) (CoreumToken, error) {
tokens, err := c.GetCoreumTokens(ctx)
if err != nil {
return CoreumToken{}, err
}
for _, token := range tokens {
if token.Denom == denom {
return token, nil
}
}

return CoreumToken{}, errors.Errorf("token not found in the registered tokens list, denom:%s", denom)
}

// GetCoreumTokenByXRPLCurrency returns a coreum registered token or nil by the provided xrpl currency.
func (c *ContractClient) GetCoreumTokenByXRPLCurrency(ctx context.Context, xrplCurrency string) (CoreumToken, error) {
// TODO(dzmitryhil) use new query function from the contract once we create it
tokens, err := c.GetCoreumTokens(ctx)
if err != nil {
return CoreumToken{}, err
}
for _, token := range tokens {
if token.XRPLCurrency == xrplCurrency {
return token, nil
}
}

return CoreumToken{}, errors.Errorf("token not found in the registered tokens list, xrplCurrency:%s", xrplCurrency)
}

// GetCoreumTokens returns a list of all coreum tokens.
func (c *ContractClient) GetCoreumTokens(ctx context.Context) ([]CoreumToken, error) {
tokens := make([]CoreumToken, 0)
Expand Down
2 changes: 1 addition & 1 deletion relayer/logger/zap.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func NewZapLogger(cfg ZapLoggerConfig) (*ZapLogger, error) {

zapLogger, err := zapCfg.Build(zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
if err != nil {
return nil, errors.Wrapf(err, "failed to build zap logger form the config, config:%+v", zapCfg)
return nil, errors.Wrapf(err, "failed to build zap logger from the config, config:%+v", zapCfg)
}

return &ZapLogger{
Expand Down
11 changes: 10 additions & 1 deletion relayer/processes/amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,16 @@ func ConvertXRPLOriginatedTokenCoreumAmountToXRPLAmount(coreumAmount sdkmath.Int
}, nil
}

tenPowerDec := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(XRPLIssuedCurrencyDecimals)), nil)
return convertCoreumAmountToXRPLAmountWithDecimals(coreumAmount, XRPLIssuedCurrencyDecimals, issuerString, currencyString)
}

// ConvertCoreumOriginatedTokenCoreumAmountToXRPLAmount converts the coreum originated token amount to XRPL amount based on decimals.
func ConvertCoreumOriginatedTokenCoreumAmountToXRPLAmount(coreumAmount sdkmath.Int, decimals uint32, issuerString, currencyString string) (rippledata.Amount, error) {
return convertCoreumAmountToXRPLAmountWithDecimals(coreumAmount, decimals, issuerString, currencyString)
}

func convertCoreumAmountToXRPLAmountWithDecimals(coreumAmount sdkmath.Int, decimals uint32, issuerString, currencyString string) (rippledata.Amount, error) {
tenPowerDec := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)
floatAmount := big.NewFloat(0).SetRat(big.NewRat(0, 1).SetFrac(coreumAmount.BigInt(), tenPowerDec))
// format with exponent
amountString := fmt.Sprintf("%s/%s/%s", floatAmount.Text('g', XRPLAmountPrec), currencyString, issuerString)
Expand Down
Loading

0 comments on commit 52558e7

Please sign in to comment.