| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- package e2e
- import (
- "context"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "math"
- "math/big"
- "net/http"
- "net/url"
- "testing"
- "time"
- "github.com/certusone/wormhole/node/pkg/devnet"
- "github.com/certusone/wormhole/node/pkg/ethereum"
- "github.com/certusone/wormhole/node/pkg/ethereum/erc20"
- "github.com/certusone/wormhole/node/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))
- }
|