e2e: remove

These were testing the token bridge and do not currently pass
(or even compile). We will bring back later some of the code.

Change-Id: I34e9ae5aa901c9b20572d1fc8d87a599fa49ad93
This commit is contained in:
Leo 2021-07-21 19:01:43 +02:00
parent abe95522b1
commit 01564150db
7 changed files with 1 additions and 1186 deletions

View File

@ -17,10 +17,6 @@ and code reviews are our most important tools to accomplish that.
- Releases are first tested on a testnet. This involves coordination with the mainnet DAO running the nodes.
- We aim for close to 100% test coverage on all critical paths. Most of Wormhole's complexity is in the
external interfaces, therefore, we primarily rely on software-in-the-loop E2E testing that exercises
the entire path. Where applicable, we also use faster unit tests for invariant checking.
- Commits should be small and have a meaningful commit message. One commit should, roughly, be "one idea" and
be as atomic as possible. A feature can consist of many such commits.
@ -50,7 +46,7 @@ The answer is... maybe? The following things are needed in order to fully suppor
- The smart contract needs to be built and audited. In some cases, existing contracts can be used, like with
EVM-compatible chains.
- Support for observing the chain needs to be added to guardiand, along with E2E tests.
- Support for observing the chain needs to be added to guardiand.
- Web wallet integration needs to be built to actually interact with Wormhole.

View File

@ -61,11 +61,6 @@ Generate test Solana -> Ethereum transfers:
scripts/send-solana-lockups.sh
Run end-to-end tests:
cd bridge
go test github.com/certusone/wormhole/bridge/e2e
Adjust number of nodes in running cluster: (this is only useful if you want to test scenarios where the number
of nodes diverges from the guardian set - otherwise, `tilt down --delete-namespaces` and restart the cluster)

View File

@ -1,262 +0,0 @@
package e2e
import (
"context"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/mr-tron/base58"
"k8s.io/client-go/kubernetes"
"github.com/certusone/wormhole/bridge/pkg/devnet"
"github.com/certusone/wormhole/bridge/pkg/ethereum"
"github.com/certusone/wormhole/bridge/pkg/vaa"
"github.com/ethereum/go-ethereum/ethclient"
)
func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.TransactOpts, *TerraClient) {
// List of pods we need in a ready state before we can run tests.
want := []string{
// Our test guardian set.
"guardian-0",
//"guardian-1",
//"guardian-2",
//"guardian-3",
//"guardian-4",
//"guardian-5",
// Connected chains
"solana-devnet-0",
"terra-terrad-0",
"terra-lcd-0",
"eth-devnet-0",
}
c := getk8sClient()
// Wait for all pods to be ready. This blocks until the bridge is ready to receive lockups.
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
waitForPods(ctx, c, want)
if ctx.Err() != nil {
t.Fatal(ctx.Err())
}
// Ethereum client.
ec, err := ethclient.Dial(devnet.GanacheRPCURL)
if err != nil {
t.Fatalf("dialing devnet eth rpc failed: %v", err)
}
kt := devnet.GetKeyedTransactor(context.Background())
// Terra client
tc, err := NewTerraClient()
if err != nil {
t.Fatalf("creating devnet terra client failed: %v", err)
}
return c, ec, kt, tc
}
// Careful about parallel tests - accounts on some chains like Ethereum cannot be
// used concurrently as they have monotonically increasing nonces that would conflict.
// Either use different Ethereum account, or do not run Ethereum tests in parallel.
func TestEndToEnd_SOL_ETH(t *testing.T) {
c, ec, kt, _ := setup(t)
t.Run("[SOL] Native -> [ETH] Wrapped", func(t *testing.T) {
testSolanaLockup(t, context.Background(), ec, c,
// Source SPL account
devnet.SolanaExampleTokenOwningAccount,
// Source SPL token
devnet.SolanaExampleToken,
// Our wrapped destination token on Ethereum
devnet.GanacheExampleERC20WrappedSOL,
// Amount of SPL token value to transfer.
50*devnet.SolanaDefaultPrecision,
// Same precision - same amount, no precision gained.
0,
)
})
t.Run("[ETH] Wrapped -> [SOL] Native", func(t *testing.T) {
testEthereumLockup(t, context.Background(), ec, kt, c,
// Source ERC20 token
devnet.GanacheExampleERC20WrappedSOL,
// Destination SPL token account
devnet.SolanaExampleTokenOwningAccount,
// Amount (the reverse of what the previous test did, with the same precision because
// the wrapped ERC20 is set to the original asset's 10**9 precision).
50*devnet.SolanaDefaultPrecision,
// No precision loss
0,
)
})
t.Run("[ETH] Native -> [SOL] Wrapped", func(t *testing.T) {
testEthereumLockup(t, context.Background(), ec, kt, c,
// Source ERC20 token
devnet.GanacheExampleERC20Token,
// Destination SPL token account
devnet.SolanaExampleWrappedERCTokenOwningAccount,
// Amount
0.000000012*devnet.ERC20DefaultPrecision,
// We lose 9 digits of precision on this path, as the default ERC20 token has 10**18 precision.
9,
)
})
t.Run("[SOL] Wrapped -> [ETH] Native", func(t *testing.T) {
testSolanaLockup(t, context.Background(), ec, c,
// Source SPL account
devnet.SolanaExampleWrappedERCTokenOwningAccount,
// Source SPL token
devnet.SolanaExampleWrappedERCToken,
// Our wrapped destination token on Ethereum
devnet.GanacheExampleERC20Token,
// Amount of SPL token value to transfer.
0.000000012*devnet.SolanaDefaultPrecision,
// We gain 9 digits of precision on Eth.
9,
)
})
}
func TestEndToEnd_SOL_Terra(t *testing.T) {
c, _, _, tc := setup(t)
t.Run("[Terra] Native -> [SOL] Wrapped", func(t *testing.T) {
testTerraLockup(t, context.Background(), tc, c,
// Source CW20 token
devnet.TerraTokenAddress,
// Destination SPL token account
devnet.SolanaExampleWrappedCWTokenOwningAccount,
// Amount
2*devnet.TerraDefaultPrecision,
// Same precision - same amount, no precision gained.
0,
)
})
t.Run("[SOL] Wrapped -> [Terra] Native", func(t *testing.T) {
testSolanaToTerraLockup(t, context.Background(), c,
// Source SPL account
devnet.SolanaExampleWrappedCWTokenOwningAccount,
// Source SPL token
devnet.SolanaExampleWrappedCWToken,
// Wrapped
false,
// Amount of SPL token value to transfer.
2*devnet.TerraDefaultPrecision,
// Same precision - same amount, no precision gained.
0,
)
})
t.Run("[SOL] Native -> [Terra] Wrapped", func(t *testing.T) {
testSolanaToTerraLockup(t, context.Background(), c,
// Source SPL account
devnet.SolanaExampleTokenOwningAccount,
// Source SPL token
devnet.SolanaExampleToken,
// Native
true,
// Amount of SPL token value to transfer.
50*devnet.SolanaDefaultPrecision,
// Same precision - same amount, no precision gained.
0,
)
})
t.Run("[Terra] Wrapped -> [SOL] Native", func(t *testing.T) {
tokenSlice, err := base58.Decode(devnet.SolanaExampleToken)
if err != nil {
t.Fatal(err)
}
wrappedAsset, err := waitTerraAsset(t, context.Background(), devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice)
if err != nil {
t.Fatal(err)
}
testTerraLockup(t, context.Background(), tc, c,
// Source wrapped token
wrappedAsset,
// Destination SPL token account
devnet.SolanaExampleTokenOwningAccount,
// Amount of Terra token value to transfer.
50*devnet.SolanaDefaultPrecision,
// Same precision
0,
)
})
}
func TestEndToEnd_ETH_Terra(t *testing.T) {
_, ec, kt, tc := setup(t)
t.Run("[Terra] Native -> [ETH] Wrapped", func(t *testing.T) {
testTerraToEthLockup(t, context.Background(), tc, ec,
// Source CW20 token
devnet.TerraTokenAddress,
// Destination ETH token
devnet.GanacheExampleERC20WrappedTerra,
// Amount
2*devnet.TerraDefaultPrecision,
// Same precision - same amount, no precision gained.
0,
)
})
t.Run("[ETH] Wrapped -> [Terra] Native", func(t *testing.T) {
testEthereumToTerraLockup(t, context.Background(), ec, kt,
// Source Ethereum token
devnet.GanacheExampleERC20WrappedTerra,
// Wrapped
false,
// Amount of Ethereum token value to transfer.
2*devnet.TerraDefaultPrecision,
// Same precision
0,
)
})
t.Run("[ETH] Native -> [Terra] Wrapped", func(t *testing.T) {
testEthereumToTerraLockup(t, context.Background(), ec, kt,
// Source Ethereum token
devnet.GanacheExampleERC20Token,
// Native
true,
// Amount of Ethereum token value to transfer.
0.000000012*devnet.ERC20DefaultPrecision,
// We lose 9 digits of precision on this path, as the default ERC20 token has 10**18 precision.
9,
)
})
t.Run("[Terra] Wrapped -> [ETH] Native", func(t *testing.T) {
paddedTokenAddress := ethereum.PadAddress(devnet.GanacheExampleERC20Token)
wrappedAsset, err := waitTerraAsset(t, context.Background(), devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:])
if err != nil {
t.Fatal(err)
}
testTerraToEthLockup(t, context.Background(), tc, ec,
// Source wrapped token
wrappedAsset,
// Destination ETH token
devnet.GanacheExampleERC20Token,
// Amount of Terra token value to transfer.
0.000000012*1e9, // 10**9 because default ETH precision is 18 and we lost 9 digits on wrapping
// We gain 9 digits of precision on Eth.
9,
)
})
}

View File

@ -1,190 +0,0 @@
package e2e
import (
"context"
"encoding/hex"
"math"
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/tendermint/tendermint/libs/rand"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"github.com/certusone/wormhole/bridge/pkg/devnet"
"github.com/certusone/wormhole/bridge/pkg/ethereum"
"github.com/certusone/wormhole/bridge/pkg/ethereum/abi"
"github.com/certusone/wormhole/bridge/pkg/ethereum/erc20"
"github.com/certusone/wormhole/bridge/pkg/vaa"
)
// waitEthBalance waits for target account before to increase.
func waitEthBalance(t *testing.T, ctx context.Context, token *erc20.Erc20, before *big.Int, target int64) {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
err := wait.PollUntil(1*time.Second, func() (bool, error) {
after, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
if err != nil {
t.Log(err)
return false, nil
}
d := new(big.Int).Sub(after, before)
t.Logf("ERC20 balance after: %d -> %d, delta %d", before, after, d)
if after.Cmp(before) != 0 {
if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
t.Errorf("expected ERC20 delta of %v, got: %v", target, d)
}
return true, nil
}
return false, nil
}, ctx.Done())
if err != nil {
t.Error(err)
}
}
func testEthereumLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, kt *bind.TransactOpts,
c *kubernetes.Clientset, tokenAddr common.Address, destination string, amount int64, precisionLoss int) {
// Bridge client
ethBridge, err := abi.NewAbi(devnet.GanacheBridgeContractAddress, ec)
if err != nil {
panic(err)
}
// Source token client
token, err := erc20.NewErc20(tokenAddr, ec)
if err != nil {
panic(err)
}
// Store balance of source ERC20 token
beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
if err != nil {
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("ERC20 balance: %v", beforeErc20)
// Store balance of destination SPL token
beforeSPL, err := getSPLBalance(ctx, c, destination)
if err != nil {
t.Fatal(err)
}
t.Logf("SPL balance: %d", beforeSPL)
// Send lockup
tx, err := ethBridge.LockAssets(kt,
// asset address
tokenAddr,
// token amount
new(big.Int).SetInt64(amount),
// recipient address on target chain
devnet.MustBase58ToEthAddress(destination),
// target chain
vaa.ChainIDSolana,
// random nonce
rand.Uint32(),
// refund dust?
false,
)
if err != nil {
t.Fatal(err)
}
t.Logf("sent lockup tx: %v", tx.Hash().Hex())
// Destination account increases by full amount.
waitSPLBalance(t, ctx, c, destination, beforeSPL, int64(float64(amount)/math.Pow10(precisionLoss)))
// Source account decreases by the full amount.
waitEthBalance(t, ctx, token, beforeErc20, -int64(amount))
}
func testEthereumToTerraLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, kt *bind.TransactOpts,
tokenAddr common.Address, isNative bool, amount int64, precisionLoss int) {
// Bridge client
ethBridge, err := abi.NewAbi(devnet.GanacheBridgeContractAddress, ec)
if err != nil {
panic(err)
}
// Source token client
token, err := erc20.NewErc20(tokenAddr, ec)
if err != nil {
panic(err)
}
// Store balance of source ERC20 token
beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
if err != nil {
beforeErc20 = new(big.Int)
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("ERC20 balance: %v", beforeErc20)
// Store balance of destination CW20 token
paddedTokenAddress := ethereum.PadAddress(tokenAddr)
var terraToken string
if isNative {
terraToken, err = getAssetAddress(ctx, devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:])
if err != nil {
t.Log(err)
}
} else {
terraToken = devnet.TerraTokenAddress
}
// Get balance if deployed
beforeCw20, err := getTerraBalance(ctx, terraToken)
if err != nil {
beforeCw20 = new(big.Int)
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("CW20 balance: %v", beforeCw20)
// Send lockup
dstAddress, err := hex.DecodeString(devnet.TerraMainTestAddressHex)
if err != nil {
t.Fatal(err)
}
var dstAddressBytes [32]byte
copy(dstAddressBytes[:], dstAddress)
tx, err := ethBridge.LockAssets(kt,
// asset address
tokenAddr,
// token amount
new(big.Int).SetInt64(amount),
// recipient address on target chain
dstAddressBytes,
// target chain
vaa.ChainIDTerra,
// random nonce
rand.Uint32(),
// refund dust?
false,
)
if err != nil {
t.Fatal(err)
}
t.Logf("sent lockup tx: %v", tx.Hash().Hex())
// Destination account increases by the full amount.
if isNative {
waitTerraUnknownBalance(t, ctx, devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:], beforeCw20, int64(float64(amount)/math.Pow10(precisionLoss)))
} else {
waitTerraBalance(t, ctx, devnet.TerraTokenAddress, beforeCw20, int64(float64(amount)/math.Pow10(precisionLoss)))
}
// Source account decreases by the full amount.
waitEthBalance(t, ctx, token, beforeErc20, -int64(amount))
}

View File

@ -1,149 +0,0 @@
package e2e
import (
"bytes"
"context"
"fmt"
"log"
"strings"
"github.com/golang/glog"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
)
const (
TiltDefaultNamespace = "wormhole" // hardcoded in Tiltfile
)
func getk8sClient() *kubernetes.Clientset {
config, err := getk8sConfig()
c, err := kubernetes.NewForConfig(config)
if err != nil {
glog.Errorln(err)
}
return c
}
func getk8sConfig() (*rest.Config, error) {
// Load local default kubeconfig.
rules := clientcmd.NewDefaultClientConfigLoadingRules()
rules.DefaultClientConfig = &clientcmd.DefaultClientConfig
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.ClientConfigLoader(rules), nil).ClientConfig()
}
func hasPodReadyCondition(conditions []corev1.PodCondition) bool {
for _, condition := range conditions {
if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
}
func waitForPods(ctx context.Context, c *kubernetes.Clientset, want []string) {
found := make(map[string]bool)
ctx, cancel := context.WithCancel(ctx)
watchlist := cache.NewListWatchFromClient(
c.CoreV1().RESTClient(),
"pods",
TiltDefaultNamespace,
fields.Everything(),
)
handle := func(pod *corev1.Pod) {
ready := hasPodReadyCondition(pod.Status.Conditions)
log.Printf("pod added/changed: %s is %s, ready: %v", pod.Name, pod.Status.Phase, ready)
if ready {
found[pod.Name] = true
}
missing := 0
for _, v := range want {
if found[v] == false {
missing += 1
}
}
if missing == 0 {
cancel()
}
}
_, controller := cache.NewInformer(
watchlist,
&corev1.Pod{},
0,
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { handle(obj.(*corev1.Pod)) },
UpdateFunc: func(oldObj, newObj interface{}) { handle(newObj.(*corev1.Pod)) },
},
)
controller.Run(ctx.Done())
}
func executeCommandInPod(ctx context.Context, c *kubernetes.Clientset, podName string, container string, cmd []string) ([]byte, error) {
p, err := c.CoreV1().Pods(TiltDefaultNamespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get pod %s: %w", p, err)
}
req := c.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(TiltDefaultNamespace).
SubResource("exec")
req = req.VersionedParams(&corev1.PodExecOptions{
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
Container: container,
Command: cmd,
}, scheme.ParameterCodec)
config, err := getk8sConfig()
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
return nil, fmt.Errorf("failed to init executor: %w", err)
}
var (
execOut bytes.Buffer
execErr bytes.Buffer
)
err = exec.Stream(remotecommand.StreamOptions{
Stdout: &execOut,
Stderr: &execErr,
Tty: false,
})
log.Printf("command: %s", strings.Join(cmd, " "))
if execErr.Len() > 0 {
log.Printf("stderr: %s", execErr.String())
}
if err != nil {
return nil, fmt.Errorf("failed to execute remote command: %w", err)
}
return execOut.Bytes(), nil
}

View File

@ -1,184 +0,0 @@
package e2e
import (
"context"
"fmt"
"math"
"math/big"
"regexp"
"strconv"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/mr-tron/base58"
"github.com/tendermint/tendermint/libs/rand"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"github.com/certusone/wormhole/bridge/pkg/devnet"
"github.com/certusone/wormhole/bridge/pkg/ethereum/erc20"
"github.com/certusone/wormhole/bridge/pkg/vaa"
)
func getSPLBalance(ctx context.Context, c *kubernetes.Clientset, hexAddr string) (*big.Int, error) {
b, err := executeCommandInPod(ctx, c, "solana-devnet-0", "setup",
[]string{"cli", "balance", hexAddr})
if err != nil {
return nil, fmt.Errorf("error running 'cli balance': %w", err)
}
re := regexp.MustCompile("(?m)^amount: (.*)$")
m := re.FindStringSubmatch(string(b))
if len(m) == 0 {
return nil, fmt.Errorf("invalid 'cli balance' output: %s", string(b))
}
n, ok := new(big.Int).SetString(m[1], 10)
if !ok {
return nil, fmt.Errorf("invalid int: %s", m[1])
}
return n, nil
}
func waitSPLBalance(t *testing.T, ctx context.Context, c *kubernetes.Clientset, hexAddr string, before *big.Int, target int64) {
// Wait for target account balance to increase.
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
err := wait.PollUntil(1*time.Second, func() (bool, error) {
after, err := getSPLBalance(ctx, c, hexAddr)
if err != nil {
t.Fatal(err)
}
d := new(big.Int).Sub(after, before)
t.Logf("SPL balance after: %d -> %d, delta %d", before, after, d)
if after.Cmp(before) != 0 {
if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
t.Errorf("expected SPL delta of %v, got: %v", target, d)
}
return true, nil
}
return false, nil
}, ctx.Done())
if err != nil {
t.Error(err)
}
}
func testSolanaLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, c *kubernetes.Clientset,
sourceAcct string, tokenAddr string, destination common.Address, amount int, precisionGain int) {
token, err := erc20.NewErc20(destination, ec)
if err != nil {
panic(err)
}
// Store balance of wrapped destination token
beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
if err != nil {
beforeErc20 = new(big.Int)
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("ERC20 balance: %v", beforeErc20)
// Store balance of source SPL token
beforeSPL, err := getSPLBalance(ctx, c, sourceAcct)
if err != nil {
t.Fatal(err)
}
t.Logf("SPL balance: %d", beforeSPL)
_, err = executeCommandInPod(ctx, c, "solana-devnet-0", "setup",
[]string{"cli", "lock",
// Address of the Wormhole bridge.
devnet.SolanaBridgeContract,
// Account which holds the SPL tokens to be sent.
sourceAcct,
// The SPL token.
tokenAddr,
// Token amount.
strconv.Itoa(amount),
// Destination chain ID.
strconv.Itoa(vaa.ChainIDEthereum),
// Random nonce.
strconv.Itoa(int(rand.Uint16())),
// Destination account on Ethereum
devnet.GanacheClientDefaultAccountAddress.Hex()[2:],
})
if err != nil {
t.Fatal(err)
}
// Destination account increases by the full amount.
waitEthBalance(t, ctx, token, beforeErc20, int64(float64(amount)*math.Pow10(precisionGain)))
// Source account decreases by full amount.
waitSPLBalance(t, ctx, c, sourceAcct, beforeSPL, -int64(amount))
}
func testSolanaToTerraLockup(t *testing.T, ctx context.Context, c *kubernetes.Clientset,
sourceAcct string, tokenAddr string, isNative bool, amount int, precisionGain int) {
tokenSlice, err := base58.Decode(tokenAddr)
if err != nil {
t.Fatal(err)
}
var terraToken string
if isNative {
terraToken, err = getAssetAddress(ctx, devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice)
if err != nil {
t.Log(err)
}
} else {
terraToken = devnet.TerraTokenAddress
}
// Get balance if deployed
beforeCw20, err := getTerraBalance(ctx, terraToken)
if err != nil {
beforeCw20 = new(big.Int)
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("CW20 balance: %v", beforeCw20)
// Store balance of source SPL token
beforeSPL, err := getSPLBalance(ctx, c, sourceAcct)
if err != nil {
t.Fatal(err)
}
t.Logf("SPL balance: %d", beforeSPL)
_, err = executeCommandInPod(ctx, c, "solana-devnet-0", "setup",
[]string{"cli", "lock",
// Address of the Wormhole bridge.
devnet.SolanaBridgeContract,
// Account which holds the SPL tokens to be sent.
sourceAcct,
// The SPL token.
tokenAddr,
// Token amount.
strconv.Itoa(amount),
// Destination chain ID.
strconv.Itoa(vaa.ChainIDTerra),
// Random nonce.
strconv.Itoa(int(rand.Uint16())),
// Destination account on Terra
devnet.TerraMainTestAddressHex,
})
if err != nil {
t.Fatal(err)
}
// Source account decreases by full amount.
waitSPLBalance(t, ctx, c, sourceAcct, beforeSPL, -int64(amount))
// Destination account increases by the full amount.
if isNative {
waitTerraUnknownBalance(t, ctx, devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice, beforeCw20, int64(float64(amount)*math.Pow10(precisionGain)))
} else {
waitTerraBalance(t, ctx, devnet.TerraTokenAddress, beforeCw20, int64(float64(amount)*math.Pow10(precisionGain)))
}
}

View File

@ -1,391 +0,0 @@
package e2e
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"math/big"
"net/http"
"net/url"
"testing"
"time"
"github.com/certusone/wormhole/bridge/pkg/devnet"
"github.com/certusone/wormhole/bridge/pkg/ethereum"
"github.com/certusone/wormhole/bridge/pkg/ethereum/erc20"
"github.com/certusone/wormhole/bridge/pkg/vaa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/tendermint/tendermint/libs/rand"
"github.com/terra-project/terra.go/client"
"github.com/terra-project/terra.go/key"
"github.com/terra-project/terra.go/msg"
"github.com/terra-project/terra.go/tx"
"github.com/tidwall/gjson"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
)
type lockAssetsMsg struct {
Params lockAssetsParams `json:"lock_assets"`
}
type increaseAllowanceMsg struct {
Params increaseAllowanceParams `json:"increase_allowance"`
}
type lockAssetsParams struct {
Asset string `json:"asset"`
Amount string `json:"amount"`
Recipient []byte `json:"recipient"`
TargetChain uint8 `json:"target_chain"`
Nonce uint32 `json:"nonce"`
}
type increaseAllowanceParams struct {
Spender string `json:"spender"`
Amount string `json:"amount"`
}
// TerraClient encapsulates Terra LCD client and fee payer signing address
type TerraClient struct {
lcdClient client.LCDClient
address msg.AccAddress
}
const (
feeAmount = 10000
feeDenomination = "uluna"
)
func (tc TerraClient) lockAssets(t *testing.T, ctx context.Context, token string, amount *big.Int, recipient [32]byte, targetChain uint8, nonce uint32) (*client.TxResponse, error) {
bridgeContract, err := msg.AccAddressFromBech32(devnet.TerraBridgeAddress)
if err != nil {
return nil, err
}
tokenContract, err := msg.AccAddressFromBech32(token)
if err != nil {
return nil, err
}
// Create tx
increaseAllowanceCall, err := json.Marshal(increaseAllowanceMsg{
Params: increaseAllowanceParams{
Spender: devnet.TerraBridgeAddress,
Amount: amount.String(),
}})
if err != nil {
return nil, err
}
lockAssetsCall, err := json.Marshal(lockAssetsMsg{
Params: lockAssetsParams{
Asset: token,
Amount: amount.String(),
Recipient: recipient[:],
TargetChain: targetChain,
Nonce: nonce,
}})
if err != nil {
return nil, err
}
t.Logf("increaseAllowanceCall\n %s", increaseAllowanceCall)
t.Logf("lockAssetsCall\n %s", lockAssetsCall)
executeIncreaseAllowance := msg.NewExecuteContract(tc.address, tokenContract, increaseAllowanceCall, msg.NewCoins())
executeLockAssets := msg.NewExecuteContract(tc.address, bridgeContract, lockAssetsCall, msg.NewCoins(msg.NewInt64Coin(feeDenomination, feeAmount)))
transaction, err := tc.lcdClient.CreateAndSignTx(ctx, client.CreateTxOptions{
Msgs: []msg.Msg{
executeIncreaseAllowance,
executeLockAssets,
},
Fee: tx.StdFee{
Gas: msg.NewInt(0),
Amount: msg.NewCoins(),
},
})
if err != nil {
return nil, err
}
// Broadcast
return tc.lcdClient.Broadcast(ctx, transaction)
}
// NewTerraClient creates new TerraClient instance to work
func NewTerraClient() (*TerraClient, error) {
// Derive Raw Private Key
privKey, err := key.DerivePrivKey(devnet.TerraFeePayerKey, key.CreateHDPath(0, 0))
if err != nil {
return nil, err
}
// Generate StdPrivKey
tmKey, err := key.StdPrivKeyGen(privKey)
if err != nil {
return nil, err
}
// Generate Address from Public Key
address := msg.AccAddress(tmKey.PubKey().Address())
// Terra client
lcdClient := client.NewLCDClient(
devnet.TerraLCDURL,
devnet.TerraChainID,
msg.NewDecCoinFromDec("uusd", msg.NewDecFromIntWithPrec(msg.NewInt(15), 2)), // 0.15uusd
msg.NewDecFromIntWithPrec(msg.NewInt(15), 1), tmKey, time.Second*15,
)
return &TerraClient{
lcdClient: *lcdClient,
address: address,
}, nil
}
func getTerraBalance(ctx context.Context, token string) (*big.Int, error) {
json, err := terraQuery(ctx, token, fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", devnet.TerraMainTestAddress))
if err != nil {
return nil, err
}
balance := gjson.Get(json, "result.balance").String()
parsed, success := new(big.Int).SetString(balance, 10)
if !success {
return nil, fmt.Errorf("cannot parse balance: %s", balance)
}
return parsed, nil
}
func getAssetAddress(ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
json, err := terraQuery(ctx, contract, fmt.Sprintf("{\"wrapped_registry\":{\"chain\":%d,\"address\":\"%s\"}}",
chain,
base64.StdEncoding.EncodeToString(asset)))
if err != nil {
return "", err
}
return gjson.Get(json, "result.address").String(), nil
}
func terraQuery(ctx context.Context, contract string, query string) (string, error) {
requestURL := fmt.Sprintf("%s/wasm/contracts/%s/store?query_msg=%s", devnet.TerraLCDURL, contract, url.QueryEscape(query))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
if err != nil {
return "", fmt.Errorf("http request error: %w", err)
}
client := &http.Client{
Timeout: time.Second * 15,
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("http execution error: %w", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("http read error: %w", err)
}
return string(body), nil
}
// waitTerraAsset waits for asset contract to be deployed on terra
func waitTerraAsset(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
defer cancel()
assetAddress := ""
err := wait.PollUntil(3*time.Second, func() (bool, error) {
address, err := getAssetAddress(ctx, contract, chain, asset)
if err != nil {
t.Log(err)
return false, nil
}
// Check the case if request was successful, but asset address is not yet in the registry
if address == "" {
return false, nil
}
t.Logf("Returning asset: %s", address)
assetAddress = address
return true, nil
}, ctx.Done())
if err != nil {
t.Error(err)
}
return assetAddress, err
}
// waitTerraBalance waits for target account before to increase.
func waitTerraBalance(t *testing.T, ctx context.Context, token string, before *big.Int, target int64) {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
err := wait.PollUntil(1*time.Second, func() (bool, error) {
after, err := getTerraBalance(ctx, token)
if err != nil {
return false, err
}
d := new(big.Int).Sub(after, before)
t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
if after.Cmp(before) != 0 {
if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
t.Errorf("expected CW20 delta of %v, got: %v", target, d)
}
return true, nil
}
return false, nil
}, ctx.Done())
if err != nil {
t.Error(err)
}
}
func waitTerraUnknownBalance(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte, before *big.Int, target int64) {
token, err := waitTerraAsset(t, ctx, contract, chain, asset)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
defer cancel()
err = wait.PollUntil(3*time.Second, func() (bool, error) {
after, err := getTerraBalance(ctx, token)
if err != nil {
return false, err
}
d := new(big.Int).Sub(after, before)
t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
if after.Cmp(before) != 0 {
if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
t.Errorf("expected CW20 delta of %v, got: %v", target, d)
}
return true, nil
}
return false, nil
}, ctx.Done())
if err != nil {
t.Error(err)
}
}
func testTerraLockup(t *testing.T, ctx context.Context, tc *TerraClient,
c *kubernetes.Clientset, token string, destination string, amount int64, precisionLoss int) {
// Store balance of source CW20 token
beforeCw20, err := getTerraBalance(ctx, token)
if err != nil {
t.Log(err) // account may not yet exist, defaults to 0
}
t.Logf("CW20 balance: %v", beforeCw20)
// Store balance of destination SPL token
beforeSPL, err := getSPLBalance(ctx, c, destination)
if err != nil {
t.Fatal(err)
}
t.Logf("SPL balance: %d", beforeSPL)
// Send lockup
tx, err := tc.lockAssets(
t, ctx,
// asset address
token,
// token amount
new(big.Int).SetInt64(amount),
// recipient address on target chain
devnet.MustBase58ToEthAddress(destination),
// target chain
vaa.ChainIDSolana,
// random nonce
rand.Uint32(),
)
if err != nil {
t.Error(err)
}
t.Logf("sent lockup tx: %s", tx.TxHash)
// Destination account increases by full amount.
waitSPLBalance(t, ctx, c, destination, beforeSPL, int64(float64(amount)/math.Pow10(precisionLoss)))
// Source account decreases by the full amount.
waitTerraBalance(t, ctx, token, beforeCw20, -int64(amount))
}
func testTerraToEthLockup(t *testing.T, ctx context.Context, tc *TerraClient,
ec *ethclient.Client, tokenAddr string, destination common.Address, amount int64, precisionGain int) {
token, err := erc20.NewErc20(destination, ec)
if err != nil {
panic(err)
}
// Store balance of source CW20 token
beforeCw20, err := getTerraBalance(ctx, tokenAddr)
if err != nil {
t.Log(err) // account may not yet exist, defaults to 0
beforeCw20 = new(big.Int)
}
t.Logf("CW20 balance: %v", beforeCw20)
/// Store balance of wrapped destination token
beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
if err != nil {
t.Log(err) // account may not yet exist, defaults to 0
beforeErc20 = new(big.Int)
}
t.Logf("ERC20 balance: %v", beforeErc20)
// Send lockup
tx, err := tc.lockAssets(
t, ctx,
// asset address
tokenAddr,
// token amount
new(big.Int).SetInt64(amount),
// recipient address on target chain
ethereum.PadAddress(devnet.GanacheClientDefaultAccountAddress),
// target chain
vaa.ChainIDEthereum,
// random nonce
rand.Uint32(),
)
if err != nil {
t.Error(err)
}
t.Logf("sent lockup tx: %s", tx.TxHash)
// Destination account increases by full amount.
waitEthBalance(t, ctx, token, beforeErc20, int64(float64(amount)*math.Pow10(precisionGain)))
// Source account decreases by the full amount.
waitTerraBalance(t, ctx, tokenAddr, beforeCw20, -int64(amount))
}