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

feat(oracle): register new token and set up feeder through assets-module #159

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion precompiles/assets/IAssets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ interface IAssets {
uint8 decimals,
uint256 tvlLimit,
string calldata name,
string calldata metaData
string calldata metaData,
string calldata oracleInfo
) external returns (bool success, bool updated);

/// QUERIES
Expand Down
111 changes: 58 additions & 53 deletions precompiles/assets/abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,60 +156,65 @@
{
"name": "metaData",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "updated",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
"internalType": "string"
},
{
"name": "oracleInfo",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "updated",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "withdrawPrincipal",
"inputs": [
{
"name": "clientChainID",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "assetsAddress",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "withdrawAddress",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "opAmount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "latestAssetState",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
"type": "function",
"name": "withdrawPrincipal",
"inputs": [
{
"name": "clientChainID",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "assetsAddress",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "withdrawAddress",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "opAmount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "latestAssetState",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
}
]
2 changes: 1 addition & 1 deletion precompiles/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 {
return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name))
}

// Run executes the precompiled contract deposit methods defined in the ABI.
// Run executes the precompiled contract methods defined in the ABI.
func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) {
ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
if err != nil {
Expand Down
31 changes: 23 additions & 8 deletions precompiles/assets/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,38 @@ func (p Precompile) RegisterOrUpdateTokens(
}

// parse inputs
asset, err := p.TokenFromInputs(ctx, args)
asset, oInfo, err := p.TokenFromInputs(ctx, args)
if err != nil {
return nil, err
}

// the price feed must exist
_, assetID := assetstypes.GetStakeIDAndAssetIDFromStr(asset.LayerZeroChainID, "", asset.Address)
if _, err := p.assetsKeeper.GetSpecifiedAssetsPrice(ctx, assetID); err != nil {
return nil, err
oInfo.AssetID = assetID
updated := false
stakingAsset, _ := p.assetsKeeper.GetStakingAssetInfo(ctx, assetID)
if stakingAsset != nil {
// this is for update
if asset.TotalSupply.IsPositive() {
stakingAsset.AssetBasicInfo.TotalSupply = asset.TotalSupply
}
if len(asset.MetaInfo) > 0 {
stakingAsset.AssetBasicInfo.MetaInfo = asset.MetaInfo
}
updated = true
} else {
stakingAsset = &assetstypes.StakingAssetInfo{
AssetBasicInfo: &asset,
StakingTotalAmount: sdkmath.NewInt(0),
}

if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, &oInfo); err != nil {
return nil, err
}
}

updated := p.assetsKeeper.IsStakingAsset(ctx, assetID)
// this is where the magic happens
if err := p.assetsKeeper.SetStakingAssetInfo(ctx, &assetstypes.StakingAssetInfo{
AssetBasicInfo: &asset,
StakingTotalAmount: sdkmath.NewInt(0),
}); err != nil {
if err := p.assetsKeeper.SetStakingAssetInfo(ctx, stakingAsset); err != nil {
return nil, err
}

Expand Down
67 changes: 54 additions & 13 deletions precompiles/assets/types.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package assets

import (
"errors"
"fmt"
"math/big"
"regexp"
"strings"

"github.com/ethereum/go-ethereum/common/hexutil"

sdkmath "cosmossdk.io/math"
exocmn "github.com/ExocoreNetwork/exocore/precompiles/common"
assetskeeper "github.com/ExocoreNetwork/exocore/x/assets/keeper"
"github.com/ExocoreNetwork/exocore/x/assets/types"
oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types"
sdk "github.com/cosmos/cosmos-sdk/types"
cmn "github.com/evmos/evmos/v14/precompiles/common"
)

// oracleInfo: '[tokenName],[chainName],[tokenDecimal](,[interval],[contract](,[ChainDesc:{...}],[TokenDesc:{...}]))'
var tokenDescMatcher = regexp.MustCompile(`TokenDesc:{(.+?)}`)

Check failure on line 22 in precompiles/assets/types.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

File is not `gofumpt`-ed (gofumpt)
var chainDescMatcher = regexp.MustCompile(`ChainDesc:{(.+?)}`)

func (p Precompile) DepositWithdrawParamsFromInputs(ctx sdk.Context, args []interface{}) (*assetskeeper.DepositWithdrawParams, error) {
inputsLen := len(p.ABI.Methods[MethodDepositTo].Inputs)
if len(args) != inputsLen {
Expand Down Expand Up @@ -108,63 +116,96 @@
return &clientChain, nil
}

func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, error) {
func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, oracletypes.OracleInfo, error) {
inputsLen := len(p.ABI.Methods[MethodRegisterOrUpdateTokens].Inputs)
if len(args) != inputsLen {
return types.AssetInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args))
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args))
}
asset := types.AssetInfo{}
oracleInfo := oracletypes.OracleInfo{}

clientChainID, ok := args[0].(uint32)
if !ok {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0])
}
asset.LayerZeroChainID = uint64(clientChainID)
info, err := p.assetsKeeper.GetClientChainInfoByIndex(ctx, asset.LayerZeroChainID)
if err != nil {
return types.AssetInfo{}, err
return types.AssetInfo{}, oracletypes.OracleInfo{}, err
}
clientChainAddrLength := info.AddressLength

assetAddr, ok := args[1].([]byte)
if !ok || assetAddr == nil {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1])
}
if uint32(len(assetAddr)) < clientChainAddrLength {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength)
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength)
}
asset.Address = hexutil.Encode(assetAddr[:clientChainAddrLength])

decimal, ok := args[2].(uint8)
if !ok {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "uint8", args[2])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "uint8", args[2])
}
asset.Decimals = uint32(decimal)

tvlLimit, ok := args[3].(*big.Int)
if !ok || tvlLimit == nil || !(tvlLimit.Cmp(big.NewInt(0)) == 1) {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 3, "*big.Int", args[3])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 3, "*big.Int", args[3])
}
asset.TotalSupply = sdkmath.NewIntFromBigInt(tvlLimit)

name, ok := args[4].(string)
if !ok {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4])
}
if name == "" || len(name) > types.MaxChainTokenNameLength {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength)
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength)
}
asset.Name = name

metaInfo, ok := args[5].(string)
if !ok {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5])
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5])
}
if metaInfo == "" || len(metaInfo) > types.MaxChainTokenMetaInfoLength {
return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength)
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength)
}
asset.MetaInfo = metaInfo

return asset, nil
oracleInfoStr, ok := args[6].(string)
if !ok {
return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 6, "string", args[6])
}
parsed := strings.Split(oracleInfoStr, ",")
l := len(parsed)
switch {
case l > 5:
joined := strings.Join(parsed[5:], "")
tokenDesc := tokenDescMatcher.FindStringSubmatch(joined)
chainDesc := chainDescMatcher.FindStringSubmatch(joined)
if len(tokenDesc) == 2 {
oracleInfo.Token.Desc = tokenDesc[1]
}
if len(chainDesc) == 2 {
oracleInfo.Chain.Desc = chainDesc[1]
}
case l >= 5:
oracleInfo.Token.Contract = parsed[4]
fallthrough
case l >= 4:
oracleInfo.Feeder.Interval = parsed[3]
fallthrough
case l >= 3:
oracleInfo.Token.Name = parsed[0]
oracleInfo.Chain.Name = parsed[1]
oracleInfo.Token.Decimal = parsed[2]
default:
return types.AssetInfo{}, oracletypes.OracleInfo{}, errors.New(exocmn.ErrInvalidOracleInfo)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider simplifying the parsing logic for clarity.

The current switch-case structure for parsing oracleInfoStr can be simplified for better readability. Consider using a more straightforward approach to handle different lengths of parsed data.

parsed := strings.Split(oracleInfoStr, ",")
if len(parsed) < 3 {
    return types.AssetInfo{}, oracletypes.OracleInfo{}, errors.New(exocmn.ErrInvalidOracleInfo)
}

oracleInfo.Token.Name = parsed[0]
oracleInfo.Chain.Name = parsed[1]
oracleInfo.Token.Decimal = parsed[2]

if len(parsed) >= 4 {
    oracleInfo.Feeder.Interval = parsed[3]
}
if len(parsed) >= 5 {
    oracleInfo.Token.Contract = parsed[4]
}

if len(parsed) > 5 {
    joined := strings.Join(parsed[5:], "")
    tokenDesc := tokenDescMatcher.FindStringSubmatch(joined)
    chainDesc := chainDescMatcher.FindStringSubmatch(joined)
    if len(tokenDesc) == 2 {
        oracleInfo.Token.Desc = tokenDesc[1]
    }
    if len(chainDesc) == 2 {
        oracleInfo.Chain.Desc = chainDesc[1]
    }
}


return asset, oracleInfo, nil
}

func (p Precompile) ClientChainIDFromInputs(_ sdk.Context, args []interface{}) (uint32, error) {
Expand Down
2 changes: 2 additions & 0 deletions precompiles/common/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ const (
ErrInvalidNameLength = "nil name or too long for chain or token,value:%s,actualLength:%d,max:%d"

ErrInvalidEVMAddr = "the address is an invalid EVM address, addr:%s"

ErrInvalidOracleInfo = "oracle info is invalid, need at least three fields not empty: token.Name, Chain.Name, token.Decimal"
)
1 change: 1 addition & 0 deletions x/assets/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import (

type OracleKeeper interface {
GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (oracletypes.Price, error)
RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *oracletypes.OracleInfo) error
}
Loading
Loading