terra.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. package e2e
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "math"
  9. "math/big"
  10. "net/http"
  11. "net/url"
  12. "testing"
  13. "time"
  14. "github.com/certusone/wormhole/node/pkg/devnet"
  15. "github.com/certusone/wormhole/node/pkg/ethereum"
  16. "github.com/certusone/wormhole/node/pkg/ethereum/erc20"
  17. "github.com/certusone/wormhole/node/pkg/vaa"
  18. "github.com/ethereum/go-ethereum/common"
  19. "github.com/ethereum/go-ethereum/ethclient"
  20. "github.com/tendermint/tendermint/libs/rand"
  21. "github.com/terra-project/terra.go/client"
  22. "github.com/terra-project/terra.go/key"
  23. "github.com/terra-project/terra.go/msg"
  24. "github.com/terra-project/terra.go/tx"
  25. "github.com/tidwall/gjson"
  26. "k8s.io/apimachinery/pkg/util/wait"
  27. "k8s.io/client-go/kubernetes"
  28. )
  29. type lockAssetsMsg struct {
  30. Params lockAssetsParams `json:"lock_assets"`
  31. }
  32. type increaseAllowanceMsg struct {
  33. Params increaseAllowanceParams `json:"increase_allowance"`
  34. }
  35. type lockAssetsParams struct {
  36. Asset string `json:"asset"`
  37. Amount string `json:"amount"`
  38. Recipient []byte `json:"recipient"`
  39. TargetChain uint8 `json:"target_chain"`
  40. Nonce uint32 `json:"nonce"`
  41. }
  42. type increaseAllowanceParams struct {
  43. Spender string `json:"spender"`
  44. Amount string `json:"amount"`
  45. }
  46. // TerraClient encapsulates Terra LCD client and fee payer signing address
  47. type TerraClient struct {
  48. lcdClient client.LCDClient
  49. address msg.AccAddress
  50. }
  51. const (
  52. feeAmount = 10000
  53. feeDenomination = "uluna"
  54. )
  55. 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) {
  56. bridgeContract, err := msg.AccAddressFromBech32(devnet.TerraBridgeAddress)
  57. if err != nil {
  58. return nil, err
  59. }
  60. tokenContract, err := msg.AccAddressFromBech32(token)
  61. if err != nil {
  62. return nil, err
  63. }
  64. // Create tx
  65. increaseAllowanceCall, err := json.Marshal(increaseAllowanceMsg{
  66. Params: increaseAllowanceParams{
  67. Spender: devnet.TerraBridgeAddress,
  68. Amount: amount.String(),
  69. }})
  70. if err != nil {
  71. return nil, err
  72. }
  73. lockAssetsCall, err := json.Marshal(lockAssetsMsg{
  74. Params: lockAssetsParams{
  75. Asset: token,
  76. Amount: amount.String(),
  77. Recipient: recipient[:],
  78. TargetChain: targetChain,
  79. Nonce: nonce,
  80. }})
  81. if err != nil {
  82. return nil, err
  83. }
  84. t.Logf("increaseAllowanceCall\n %s", increaseAllowanceCall)
  85. t.Logf("lockAssetsCall\n %s", lockAssetsCall)
  86. executeIncreaseAllowance := msg.NewExecuteContract(tc.address, tokenContract, increaseAllowanceCall, msg.NewCoins())
  87. executeLockAssets := msg.NewExecuteContract(tc.address, bridgeContract, lockAssetsCall, msg.NewCoins(msg.NewInt64Coin(feeDenomination, feeAmount)))
  88. transaction, err := tc.lcdClient.CreateAndSignTx(ctx, client.CreateTxOptions{
  89. Msgs: []msg.Msg{
  90. executeIncreaseAllowance,
  91. executeLockAssets,
  92. },
  93. Fee: tx.StdFee{
  94. Gas: msg.NewInt(0),
  95. Amount: msg.NewCoins(),
  96. },
  97. })
  98. if err != nil {
  99. return nil, err
  100. }
  101. // Broadcast
  102. return tc.lcdClient.Broadcast(ctx, transaction)
  103. }
  104. // NewTerraClient creates new TerraClient instance to work
  105. func NewTerraClient() (*TerraClient, error) {
  106. // Derive Raw Private Key
  107. privKey, err := key.DerivePrivKey(devnet.TerraFeePayerKey, key.CreateHDPath(0, 0))
  108. if err != nil {
  109. return nil, err
  110. }
  111. // Generate StdPrivKey
  112. tmKey, err := key.StdPrivKeyGen(privKey)
  113. if err != nil {
  114. return nil, err
  115. }
  116. // Generate Address from Public Key
  117. address := msg.AccAddress(tmKey.PubKey().Address())
  118. // Terra client
  119. lcdClient := client.NewLCDClient(
  120. devnet.TerraLCDURL,
  121. devnet.TerraChainID,
  122. msg.NewDecCoinFromDec("uusd", msg.NewDecFromIntWithPrec(msg.NewInt(15), 2)), // 0.15uusd
  123. msg.NewDecFromIntWithPrec(msg.NewInt(15), 1), tmKey, time.Second*15,
  124. )
  125. return &TerraClient{
  126. lcdClient: *lcdClient,
  127. address: address,
  128. }, nil
  129. }
  130. func getTerraBalance(ctx context.Context, token string) (*big.Int, error) {
  131. json, err := terraQuery(ctx, token, fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", devnet.TerraMainTestAddress))
  132. if err != nil {
  133. return nil, err
  134. }
  135. balance := gjson.Get(json, "result.balance").String()
  136. parsed, success := new(big.Int).SetString(balance, 10)
  137. if !success {
  138. return nil, fmt.Errorf("cannot parse balance: %s", balance)
  139. }
  140. return parsed, nil
  141. }
  142. func getAssetAddress(ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
  143. json, err := terraQuery(ctx, contract, fmt.Sprintf("{\"wrapped_registry\":{\"chain\":%d,\"address\":\"%s\"}}",
  144. chain,
  145. base64.StdEncoding.EncodeToString(asset)))
  146. if err != nil {
  147. return "", err
  148. }
  149. return gjson.Get(json, "result.address").String(), nil
  150. }
  151. func terraQuery(ctx context.Context, contract string, query string) (string, error) {
  152. requestURL := fmt.Sprintf("%s/wasm/contracts/%s/store?query_msg=%s", devnet.TerraLCDURL, contract, url.QueryEscape(query))
  153. req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
  154. if err != nil {
  155. return "", fmt.Errorf("http request error: %w", err)
  156. }
  157. client := &http.Client{
  158. Timeout: time.Second * 15,
  159. }
  160. resp, err := client.Do(req)
  161. if err != nil {
  162. return "", fmt.Errorf("http execution error: %w", err)
  163. }
  164. body, err := ioutil.ReadAll(resp.Body)
  165. if err != nil {
  166. return "", fmt.Errorf("http read error: %w", err)
  167. }
  168. return string(body), nil
  169. }
  170. // waitTerraAsset waits for asset contract to be deployed on terra
  171. func waitTerraAsset(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
  172. ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
  173. defer cancel()
  174. assetAddress := ""
  175. err := wait.PollUntil(3*time.Second, func() (bool, error) {
  176. address, err := getAssetAddress(ctx, contract, chain, asset)
  177. if err != nil {
  178. t.Log(err)
  179. return false, nil
  180. }
  181. // Check the case if request was successful, but asset address is not yet in the registry
  182. if address == "" {
  183. return false, nil
  184. }
  185. t.Logf("Returning asset: %s", address)
  186. assetAddress = address
  187. return true, nil
  188. }, ctx.Done())
  189. if err != nil {
  190. t.Error(err)
  191. }
  192. return assetAddress, err
  193. }
  194. // waitTerraBalance waits for target account before to increase.
  195. func waitTerraBalance(t *testing.T, ctx context.Context, token string, before *big.Int, target int64) {
  196. ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
  197. defer cancel()
  198. err := wait.PollUntil(1*time.Second, func() (bool, error) {
  199. after, err := getTerraBalance(ctx, token)
  200. if err != nil {
  201. return false, err
  202. }
  203. d := new(big.Int).Sub(after, before)
  204. t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
  205. if after.Cmp(before) != 0 {
  206. if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
  207. t.Errorf("expected CW20 delta of %v, got: %v", target, d)
  208. }
  209. return true, nil
  210. }
  211. return false, nil
  212. }, ctx.Done())
  213. if err != nil {
  214. t.Error(err)
  215. }
  216. }
  217. func waitTerraUnknownBalance(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte, before *big.Int, target int64) {
  218. token, err := waitTerraAsset(t, ctx, contract, chain, asset)
  219. if err != nil {
  220. return
  221. }
  222. ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
  223. defer cancel()
  224. err = wait.PollUntil(3*time.Second, func() (bool, error) {
  225. after, err := getTerraBalance(ctx, token)
  226. if err != nil {
  227. return false, err
  228. }
  229. d := new(big.Int).Sub(after, before)
  230. t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
  231. if after.Cmp(before) != 0 {
  232. if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
  233. t.Errorf("expected CW20 delta of %v, got: %v", target, d)
  234. }
  235. return true, nil
  236. }
  237. return false, nil
  238. }, ctx.Done())
  239. if err != nil {
  240. t.Error(err)
  241. }
  242. }
  243. func testTerraLockup(t *testing.T, ctx context.Context, tc *TerraClient,
  244. c *kubernetes.Clientset, token string, destination string, amount int64, precisionLoss int) {
  245. // Store balance of source CW20 token
  246. beforeCw20, err := getTerraBalance(ctx, token)
  247. if err != nil {
  248. t.Log(err) // account may not yet exist, defaults to 0
  249. }
  250. t.Logf("CW20 balance: %v", beforeCw20)
  251. // Store balance of destination SPL token
  252. beforeSPL, err := getSPLBalance(ctx, c, destination)
  253. if err != nil {
  254. t.Fatal(err)
  255. }
  256. t.Logf("SPL balance: %d", beforeSPL)
  257. // Send lockup
  258. tx, err := tc.lockAssets(
  259. t, ctx,
  260. // asset address
  261. token,
  262. // token amount
  263. new(big.Int).SetInt64(amount),
  264. // recipient address on target chain
  265. devnet.MustBase58ToEthAddress(destination),
  266. // target chain
  267. vaa.ChainIDSolana,
  268. // random nonce
  269. rand.Uint32(),
  270. )
  271. if err != nil {
  272. t.Error(err)
  273. }
  274. t.Logf("sent lockup tx: %s", tx.TxHash)
  275. // Destination account increases by full amount.
  276. waitSPLBalance(t, ctx, c, destination, beforeSPL, int64(float64(amount)/math.Pow10(precisionLoss)))
  277. // Source account decreases by the full amount.
  278. waitTerraBalance(t, ctx, token, beforeCw20, -int64(amount))
  279. }
  280. func testTerraToEthLockup(t *testing.T, ctx context.Context, tc *TerraClient,
  281. ec *ethclient.Client, tokenAddr string, destination common.Address, amount int64, precisionGain int) {
  282. token, err := erc20.NewErc20(destination, ec)
  283. if err != nil {
  284. panic(err)
  285. }
  286. // Store balance of source CW20 token
  287. beforeCw20, err := getTerraBalance(ctx, tokenAddr)
  288. if err != nil {
  289. t.Log(err) // account may not yet exist, defaults to 0
  290. beforeCw20 = new(big.Int)
  291. }
  292. t.Logf("CW20 balance: %v", beforeCw20)
  293. /// Store balance of wrapped destination token
  294. beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
  295. if err != nil {
  296. t.Log(err) // account may not yet exist, defaults to 0
  297. beforeErc20 = new(big.Int)
  298. }
  299. t.Logf("ERC20 balance: %v", beforeErc20)
  300. // Send lockup
  301. tx, err := tc.lockAssets(
  302. t, ctx,
  303. // asset address
  304. tokenAddr,
  305. // token amount
  306. new(big.Int).SetInt64(amount),
  307. // recipient address on target chain
  308. ethereum.PadAddress(devnet.GanacheClientDefaultAccountAddress),
  309. // target chain
  310. vaa.ChainIDEthereum,
  311. // random nonce
  312. rand.Uint32(),
  313. )
  314. if err != nil {
  315. t.Error(err)
  316. }
  317. t.Logf("sent lockup tx: %s", tx.TxHash)
  318. // Destination account increases by full amount.
  319. waitEthBalance(t, ctx, token, beforeErc20, int64(float64(amount)*math.Pow10(precisionGain)))
  320. // Source account decreases by the full amount.
  321. waitTerraBalance(t, ctx, tokenAddr, beforeCw20, -int64(amount))
  322. }