Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridge updates #2

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/spec/components/schemas/ApproveRequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ properties:
chain_id:
type: string
token_id:
type: string
type: string
amount:
type: string
format: amount.Amount
description: amount of token to approve
2 changes: 1 addition & 1 deletion docs/spec/components/schemas/LockRequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ properties:
amount:
type: string
format: amount.Amount
description: amount of token to transfer, should be presented only for fungible tokens transfer
description: amount of token to transfer, should be presented only for fungible tokens transfer or ERC1155
example: "0.001"
nft_id:
type: string
Expand Down
5 changes: 5 additions & 0 deletions internal/amount/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func (a Amount) String() string {
return stringU(a.Int())
}

func (a Amount) IsUint() bool {
i := a.Int()
return i.Mod(i, One).Cmp(big.NewInt(0)) == 0 && i.IsUint64()
}

func parseU(v string) (*big.Int, error) {
var f, o, r big.Rat

Expand Down
32 changes: 29 additions & 3 deletions internal/data/token_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ type TokenChainsQ interface {
FilterByBridgingType(types ...BridgingType) TokenChainsQ
}

type BridgingTypeQ interface {
IsWrapped() bool
}

type TokenChain struct {
TokenID string
ChainID string `fig:"chain_id,required"`
Expand All @@ -19,9 +23,31 @@ type TokenChain struct {
AutoSend bool `fig:"auto_send"`
}

type BridgingType string
type BridgingType uint8

const (
BridgingTypeLP BridgingType = "liquidity_pool"
BridgingTypeWrapped BridgingType = "wrapped"
BridgingTypeLP BridgingType = iota
BridgingTypeWrapped
BridgingTypeUSDC
)

func (b BridgingType) IsLiquidPool() bool {
if b > BridgingTypeUSDC {
panic("unsupported bridging type")
}
return b == BridgingTypeLP
}

func (b BridgingType) IsWrapped() bool {
if b > BridgingTypeUSDC {
panic("unsupported bridging type")
}
return b == BridgingTypeWrapped
}

func (b BridgingType) IsUSDC() bool {
if b > BridgingTypeUSDC {
panic("unsupported bridging type")
}
return b == BridgingTypeUSDC
}
33 changes: 21 additions & 12 deletions internal/proxy/evm/approve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import (
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"gitlab.com/distributed_lab/logan/v3/errors"
"gitlab.com/tokend/bridge/core/internal/amount"
"gitlab.com/tokend/bridge/core/internal/data"
"gitlab.com/tokend/bridge/core/internal/proxy/evm/generated/erc1155"
"gitlab.com/tokend/bridge/core/internal/proxy/evm/generated/erc20"
"gitlab.com/tokend/bridge/core/internal/proxy/evm/generated/erc721"
"math/big"
)

func (p *evmProxy) Approve(tokenChain data.TokenChain, approveFrom string) (interface{}, error) {
func (p *evmProxy) Approve(tokenChain data.TokenChain, approveFrom string, amount *amount.Amount) (interface{}, error) {
fromAddress := common.HexToAddress(approveFrom)

var tx *ethTypes.Transaction
var err error
switch tokenChain.TokenType {
case TokenTypeNative:
// Approve not needed for native token
return nil, nil
case TokenTypeErc20:
tx, err = p.approveErc20(common.HexToAddress(*tokenChain.ContractAddress), fromAddress)
tx, err = p.approveErc20(common.HexToAddress(*tokenChain.ContractAddress), fromAddress, amount)
case TokenTypeErc721:
tx, err = p.approveErc721(common.HexToAddress(*tokenChain.ContractAddress), fromAddress)
case TokenTypeErc1155:
Expand All @@ -41,24 +41,33 @@ func (p *evmProxy) Approve(tokenChain data.TokenChain, approveFrom string) (inte
return encodeTx(tx, fromAddress, p.chainID, tokenChain.ChainID, nil)
}

func (p *evmProxy) approveErc20(tokenAddress common.Address, approveFrom common.Address) (*ethTypes.Transaction, error) {
func (p *evmProxy) approveErc20(tokenAddress common.Address, approveFrom common.Address, amount *amount.Amount) (*ethTypes.Transaction, error) {
token, err := erc20.NewErc20(tokenAddress, p.client)
if err != nil {
return nil, errors.Wrap(err, "failed to create transactor")
}

amount, err := token.Allowance(&bind.CallOpts{}, approveFrom, p.bridgeContract)
decimals, err := p.getDecimals(tokenAddress.String())
if err != nil {
return nil, errors.Wrap(err, "failed to check allowance")
return nil, err
}
if amount.Cmp(big.NewInt(0)) == 1 {
// Token is already approved
return nil, nil

preparedAmount := big.NewInt(0)
preparedAmount.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10)
if amount != nil {
preparedAmount = amount.IntWithPrecision(decimals)
allowance, err := token.Allowance(&bind.CallOpts{}, approveFrom, p.bridgeContract)

if err != nil {
return nil, errors.Wrap(err, "failed to check allowance")
}
if allowance.Cmp(preparedAmount) >= 0 {
// Token is already approved
return nil, nil
}
}

uin256max := big.NewInt(0)
uin256max.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10)
tx, err := token.Approve(buildTransactOpts(approveFrom), p.bridgeContract, uin256max)
tx, err := token.Approve(buildTransactOpts(approveFrom), p.bridgeContract, preparedAmount)
if err != nil {
return nil, errors.Wrap(err, "failed to build token approve tx")
}
Expand Down
21 changes: 12 additions & 9 deletions internal/proxy/evm/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (p *evmProxy) getTxReceipt(txHash string) (*ethTypes.Receipt, error) {

func (p *evmProxy) checkNativeLockEvent(receipt *ethTypes.Receipt, eventIndex int) (*types.FungibleLockEvent, error) {
var log bridge.BridgeDepositedNative
err := getBridgeEvent(&log, depositedNativeEventName, eventIndex, receipt)
err := getBridgeEvent(&log, depositedNativeEventName, eventIndex, receipt, p.bridgeContract)
if err != nil {
return nil, err
}
Expand All @@ -91,15 +91,15 @@ func (p *evmProxy) checkNativeLockEvent(receipt *ethTypes.Receipt, eventIndex in

func (p *evmProxy) checkErc20LockEvent(receipt *ethTypes.Receipt, eventIndex int, tokenChain data.TokenChain) (*types.FungibleLockEvent, error) {
var log bridge.BridgeDepositedERC20
err := getBridgeEvent(&log, depositedERC20EventName, eventIndex, receipt)
err := getBridgeEvent(&log, depositedERC20EventName, eventIndex, receipt, p.bridgeContract)
if err != nil {
return nil, err
}

if !compareAddresses(log.Token, common.HexToAddress(*tokenChain.ContractAddress)) {
return nil, types.ErrWrongToken
}
if log.IsWrapped && tokenChain.BridgingType != data.BridgingTypeWrapped {
if log.OperationType == uint8(data.BridgingTypeWrapped) && !tokenChain.BridgingType.IsWrapped() {
return nil, types.ErrWrongLockEvent
}

Expand All @@ -117,7 +117,7 @@ func (p *evmProxy) checkErc20LockEvent(receipt *ethTypes.Receipt, eventIndex int

func (p *evmProxy) checkErc721LockEvent(receipt *ethTypes.Receipt, eventIndex int, tokenChain data.TokenChain) (*types.NonFungibleLockEvent, error) {
var log bridge.BridgeDepositedERC721
err := getBridgeEvent(&log, depositedERC721EventName, eventIndex, receipt)
err := getBridgeEvent(&log, depositedERC721EventName, eventIndex, receipt, p.bridgeContract)
if err != nil {
return nil, err
}
Expand All @@ -126,7 +126,7 @@ func (p *evmProxy) checkErc721LockEvent(receipt *ethTypes.Receipt, eventIndex in
if !compareAddresses(log.Token, tokenAddress) {
return nil, types.ErrWrongToken
}
if log.IsWrapped && tokenChain.BridgingType != data.BridgingTypeWrapped {
if log.OperationType == uint8(data.BridgingTypeWrapped) && !tokenChain.BridgingType.IsWrapped() {
return nil, types.ErrWrongLockEvent
}

Expand All @@ -139,7 +139,7 @@ func (p *evmProxy) checkErc721LockEvent(receipt *ethTypes.Receipt, eventIndex in

func (p *evmProxy) checkErc1155LockEvent(receipt *ethTypes.Receipt, eventIndex int, tokenChain data.TokenChain) (*types.NonFungibleLockEvent, error) {
var log bridge.BridgeDepositedERC1155
err := getBridgeEvent(&log, depositedERC1155EventName, eventIndex, receipt)
err := getBridgeEvent(&log, depositedERC1155EventName, eventIndex, receipt, p.bridgeContract)
if err != nil {
return nil, err
}
Expand All @@ -148,7 +148,7 @@ func (p *evmProxy) checkErc1155LockEvent(receipt *ethTypes.Receipt, eventIndex i
if !compareAddresses(log.Token, tokenAddress) {
return nil, types.ErrWrongToken
}
if log.IsWrapped && tokenChain.BridgingType != data.BridgingTypeWrapped {
if log.OperationType == uint8(data.BridgingTypeWrapped) && !tokenChain.BridgingType.IsWrapped() {
return nil, types.ErrWrongLockEvent
}
if log.Amount.Uint64() != 1 {
Expand All @@ -162,13 +162,12 @@ func (p *evmProxy) checkErc1155LockEvent(receipt *ethTypes.Receipt, eventIndex i
}, nil
}

func getBridgeEvent(dest interface{}, logName string, eventIndex int, receipt *ethTypes.Receipt) error {
func getBridgeEvent(dest interface{}, logName string, eventIndex int, receipt *ethTypes.Receipt, bridgeContract common.Address) error {
abi, err := bridge.BridgeMetaData.GetAbi()
if err != nil {
return errors.Wrap(err, "failed to parse bridge ABI")
}
contract := bind.NewBoundContract(common.Address{}, *abi, nil, nil, nil)

index := 0
for _, l := range receipt.Logs {
if l == nil {
Expand All @@ -177,6 +176,10 @@ func getBridgeEvent(dest interface{}, logName string, eventIndex int, receipt *e
err := contract.UnpackLog(dest, logName, *l)
if err == nil {
if index == eventIndex {
if !compareAddresses(l.Address, bridgeContract) {
return types.ErrWrongBridgeContract
}

return nil
}
index++
Expand Down
138 changes: 138 additions & 0 deletions internal/proxy/evm/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package evm_test

import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"gitlab.com/tokend/bridge/core/internal/data"
"gitlab.com/tokend/bridge/core/internal/data/mem"
"gitlab.com/tokend/bridge/core/internal/ipfs"
"gitlab.com/tokend/bridge/core/internal/proxy"
"gitlab.com/tokend/bridge/core/internal/proxy/evm"
"gitlab.com/tokend/bridge/core/internal/proxy/evm/signature"
"gitlab.com/tokend/bridge/core/resources"
"testing"
)

func setupEventTest() (*appConfig, error) {
qTestnetRpc := "wss://rpc-ws.qtestnet.org"
qTestnetTokenAddr := "0x340F12c1A1E30c00d502B9403328A09DD1153AbC"

tokens := []data.Token{
{
ID: "1",
Name: "USDC Testnet Token",
Symbol: "USDC",
Icon: nil,
Type: resources.FUNGIBLE,
Chains: []data.TokenChain{
{
ChainID: "qtestnet",
TokenType: evm.TokenTypeErc20,
BridgingType: data.BridgingTypeLP,
ContractAddress: &qTestnetTokenAddr,
AutoSend: false,
},
},
},
}

chains := []data.Chain{
{
ID: "qTestnet",
Name: "Q testnet",
Icon: nil,
Type: "evm",
ChainParams: nil,
Confirmations: 12,
BridgeContract: "0x0923BE8AC8F688382DF8B631978398C29b6e7474",
RpcEndpoint: qTestnetRpc,
Tokens: nil,
},
{
ID: "wrongQTestnet",
Name: "Q testnet",
Icon: nil,
Type: "evm",
ChainParams: nil,
Confirmations: 12,
BridgeContract: "",
RpcEndpoint: qTestnetRpc,
Tokens: nil,
},
}

for i, chain := range chains {
chain.Tokens = make([]data.TokenChain, 0)
chains[i] = chain
}

tokenChains := make([]data.TokenChain, 0)
for _, token := range tokens {
for i, tokenChain := range token.Chains {
tokenChain.TokenID = token.ID
token.Chains[i] = tokenChain
tokenChains = append(tokenChains, tokenChain)
for k, chain := range chains {
if chain.ID == tokenChain.ChainID {
chain.Tokens = append(chain.Tokens, tokenChain)
chains[k] = chain
}
}
}
}

ipfs := ipfs.NewClient("https://ipfs.io")
signer := "169e0067e34c4430b33113bf6806cced19b2655fd9e5b51c4847e31f5b171713"
signerPk, err := crypto.HexToECDSA(signer)
if err != nil {
return nil, err
}

proxyRepo, err := proxy.NewProxyRepo(chains, signature.NewSigner(signerPk), ipfs)
if err != nil {
return nil, err
}

config := appConfig{
ChainsQ: mem.NewChainsQ(chains),
TokensQ: mem.NewTokenQ(tokens),
TokenChainsQ: mem.NewTokenChainsQ(tokenChains),
ProxyRepo: proxyRepo,
IPFS: ipfs,
Signer: signature.NewSigner(signerPk),
}

return &config, nil
}

func TestGettingBridgeEvent(t *testing.T) {
config, err := setupEventTest()
require.NoError(t, err)

proxy := config.ProxyRepo.Get("qTestnet")
tokenChain, err := config.TokenChainsQ.
FilterByTokenID("1").
FilterByChainID("qtestnet").
Get()
require.NoError(t, err)

txHash := "0x96ebed7275fca46e55a4062309038e8d3a838296346cc3d4dd85671932b50419"
_, err = proxy.CheckFungibleLockEvent(txHash, 0, *tokenChain)
require.NoError(t, err)
}

func TestGettingBridgeEventFail(t *testing.T) {
config, err := setupEventTest()
require.NoError(t, err)

proxy := config.ProxyRepo.Get("wrongQTestnet")
tokenChain, err := config.TokenChainsQ.
FilterByTokenID("1").
FilterByChainID("qtestnet").
Get()
require.NoError(t, err)

txHash := "0x96ebed7275fca46e55a4062309038e8d3a838296346cc3d4dd85671932b50419"
_, err = proxy.CheckFungibleLockEvent(txHash, 0, *tokenChain)
require.Error(t, err)
}
Loading