From d62f23928b66746490e304eb8fd0744e0b5393b3 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Thu, 8 Aug 2024 12:43:57 +0800 Subject: [PATCH 01/13] feat(oracle): register new token and set up tokenfeeder through assets-module --- precompiles/assets/tx.go | 39 ++++++++++++--- x/assets/types/expected_keepers.go | 1 + x/oracle/keeper/params.go | 79 ++++++++++++++++++++++++++++++ x/oracle/types/params.go | 8 ++- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/precompiles/assets/tx.go b/precompiles/assets/tx.go index e76e61ec1..e93478504 100644 --- a/precompiles/assets/tx.go +++ b/precompiles/assets/tx.go @@ -3,6 +3,7 @@ package assets import ( "errors" "fmt" + "regexp" sdkmath "cosmossdk.io/math" @@ -24,6 +25,8 @@ const ( MethodIsRegisteredClientChain = "isRegisteredClientChain" ) +var oracleInfoMatcher = regexp.MustCompile(`oracle_info:{chain:(.+),\s*token:(.+),\s*decimal:(\d+)(,\s*interval:(.+))?(,\s*contract:(.+))?}`) + // DepositOrWithdraw deposit and withdraw the client chain assets for the staker, // that will change the state in assets module. func (p Precompile) DepositOrWithdraw( @@ -136,16 +139,38 @@ func (p Precompile) RegisterOrUpdateTokens( // the price feed must exist _, assetID := assetstypes.GetStakeIDAndAssetIDFromStr(asset.LayerZeroChainID, "", asset.Address) - if _, err := p.assetsKeeper.GetSpecifiedAssetsPrice(ctx, assetID); err != nil { - return nil, err + + 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), + } + + // parse oracle info from metaInfo + // MetaInfo: `...oracle_info:{chain:Bitcoin,token:BTC,decimal:8,interval:50,contract:0x}...` + oracleInfo := oracleInfoMatcher.FindStringSubmatch(asset.MetaInfo) + if len(oracleInfo) != 5 { + return nil, errors.New("register new asset fail, oracle_info must be described in meta_info") + } + // register new token for oracle module + if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, oracleInfo[1], oracleInfo[2], oracleInfo[3], oracleInfo[5], oracleInfo[7], assetID); 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 } diff --git a/x/assets/types/expected_keepers.go b/x/assets/types/expected_keepers.go index c1fe65043..2ba61ab4f 100644 --- a/x/assets/types/expected_keepers.go +++ b/x/assets/types/expected_keepers.go @@ -7,4 +7,5 @@ import ( type OracleKeeper interface { GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (oracletypes.Price, error) + RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, decimal, interval, contract, assetID string) error } diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index 5dcd5402f..f4bc95b34 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -1,10 +1,19 @@ package keeper import ( + "fmt" + "strconv" + "strings" + "github.com/ExocoreNetwork/exocore/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + startAfterBlocks = 10 + defaultInterval = 30 +) + func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.ParamsKey) // return types.NewParams() @@ -21,3 +30,73 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { bz := k.cdc.MustMarshal(¶ms) store.Set(types.ParamsKey, bz) } + +func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, decimal, interval, contract, assetID string) error { + p := k.GetParams(ctx) + if p.GetTokenIDFromAssetID(assetID) > 0 { + return fmt.Errorf("assetID exists:%s", assetID) + } + chainID := uint64(0) + for id, c := range p.Chains { + if c.Name == chain { + chainID = uint64(id) + break + } + } + if chainID == 0 { + // add new chain + p.Chains = append(p.Chains, &types.Chain{ + Name: chain, + Desc: "registered through assets module", + }) + chainID = uint64(len(p.Chains) - 1) + } + decimalInt, err := strconv.ParseInt(decimal, 10, 32) + if err != nil { + return err + } + if decimalInt < 0 { + return fmt.Errorf("decimal can't be negative:%d", decimalInt) + } + intervalInt, err := strconv.ParseUint(interval, 10, 64) + if err != nil { + return err + } + if intervalInt == 0 { + intervalInt = defaultInterval + } + + for _, t := range p.Tokens { + // token exists, bind assetID for this token + // it's possible for one price bonded with multiple assetID, like ETHUSDT from sepolia/mainnet + if t.Name == token && t.ChainID == chainID { + t.AssetID = strings.Join([]string{t.AssetID, assetID}, ",") + k.SetParams(ctx, p) + // there should have been existing tokenFeeder running(currently we register tokens from assets-module and with infinite endBlock) + return nil + } + } + + // add a new token + p.Tokens = append(p.Tokens, &types.Token{ + Name: token, + ChainID: chainID, + ContractAddress: contract, + Decimal: int32(decimalInt), + Active: true, + AssetID: assetID, + }) + // set a tokenFeeder for the new token + p.TokenFeeders = append(p.TokenFeeders, &types.TokenFeeder{ + TokenID: uint64(len(p.Tokens) - 1), + // we support rule_1 for v1 + RuleID: 1, + StartRoundID: 1, + StartBaseBlock: uint64(ctx.BlockHeight() + startAfterBlocks), + Interval: intervalInt, + EndBlock: 0, + }) + + k.SetParams(ctx, p) + return nil +} diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index 7e7368a05..3ab771690 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -2,6 +2,7 @@ package types import ( "errors" + "strings" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" @@ -484,8 +485,11 @@ func (f TokenFeeder) validate() error { func (p Params) GetTokenIDFromAssetID(assetID string) int { for id, token := range p.Tokens { - if token.AssetID == assetID { - return id + assetIDs := strings.Split(token.AssetID, ",") + for _, aID := range assetIDs { + if aID == assetID { + return id + } } } return 0 From 7c5f1ab62a9a37282ff553f316cea1f436058d09 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 13 Aug 2024 00:43:22 +0800 Subject: [PATCH 02/13] set oracle info as a new field instead of embedded into metaInfo --- precompiles/assets/IAssets.sol | 3 +- precompiles/assets/abi.json | 111 +++++++++++++++-------------- precompiles/assets/assets.go | 2 +- precompiles/assets/tx.go | 16 ++--- precompiles/assets/types.go | 57 +++++++++++---- precompiles/common/error.go | 2 + x/assets/types/expected_keepers.go | 2 +- x/oracle/keeper/params.go | 42 ++++++----- x/oracle/types/types.go | 20 ++++++ 9 files changed, 157 insertions(+), 98 deletions(-) diff --git a/precompiles/assets/IAssets.sol b/precompiles/assets/IAssets.sol index 655803410..c3a0bdb0d 100644 --- a/precompiles/assets/IAssets.sol +++ b/precompiles/assets/IAssets.sol @@ -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 diff --git a/precompiles/assets/abi.json b/precompiles/assets/abi.json index b4d0dde0c..a1d04ec2f 100644 --- a/precompiles/assets/abi.json +++ b/precompiles/assets/abi.json @@ -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" } ] diff --git a/precompiles/assets/assets.go b/precompiles/assets/assets.go index 5b725b77c..02196e634 100644 --- a/precompiles/assets/assets.go +++ b/precompiles/assets/assets.go @@ -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 { diff --git a/precompiles/assets/tx.go b/precompiles/assets/tx.go index e93478504..6531209a1 100644 --- a/precompiles/assets/tx.go +++ b/precompiles/assets/tx.go @@ -3,7 +3,6 @@ package assets import ( "errors" "fmt" - "regexp" sdkmath "cosmossdk.io/math" @@ -25,7 +24,7 @@ const ( MethodIsRegisteredClientChain = "isRegisteredClientChain" ) -var oracleInfoMatcher = regexp.MustCompile(`oracle_info:{chain:(.+),\s*token:(.+),\s*decimal:(\d+)(,\s*interval:(.+))?(,\s*contract:(.+))?}`) +// var oracleInfoMatcher = regexp.MustCompile(`oracle_info:{chain:(.+),\s*token:(.+),\s*decimal:(\d+)(,\s*interval:(.+))?(,\s*contract:(.+))?}`) // DepositOrWithdraw deposit and withdraw the client chain assets for the staker, // that will change the state in assets module. @@ -132,14 +131,14 @@ 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) - + oInfo.AssetID = assetID updated := false stakingAsset, _ := p.assetsKeeper.GetStakingAssetInfo(ctx, assetID) if stakingAsset != nil { @@ -157,14 +156,7 @@ func (p Precompile) RegisterOrUpdateTokens( StakingTotalAmount: sdkmath.NewInt(0), } - // parse oracle info from metaInfo - // MetaInfo: `...oracle_info:{chain:Bitcoin,token:BTC,decimal:8,interval:50,contract:0x}...` - oracleInfo := oracleInfoMatcher.FindStringSubmatch(asset.MetaInfo) - if len(oracleInfo) != 5 { - return nil, errors.New("register new asset fail, oracle_info must be described in meta_info") - } - // register new token for oracle module - if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, oracleInfo[1], oracleInfo[2], oracleInfo[3], oracleInfo[5], oracleInfo[7], assetID); err != nil { + if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, &oInfo); err != nil { return nil, err } } diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index 9f50a8102..8bf833993 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -1,15 +1,19 @@ package assets import ( + "errors" "fmt" "math/big" + "encoding/json" + "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" ) @@ -108,63 +112,88 @@ func (p Precompile) ClientChainInfoFromInputs(_ sdk.Context, args []interface{}) return &clientChain, nil } -func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, error) { +func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (asset types.AssetInfo, oInfo oracletypes.OracleInfo, err error) { inputsLen := len(p.ABI.Methods[MethodRegisterOrUpdateTokens].Inputs) if len(args) != inputsLen { - return types.AssetInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) + err = fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) + return asset, oInfo, err } - asset := types.AssetInfo{} clientChainID, ok := args[0].(uint32) if !ok { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0]) + return asset, oInfo, err } asset.LayerZeroChainID = uint64(clientChainID) info, err := p.assetsKeeper.GetClientChainInfoByIndex(ctx, asset.LayerZeroChainID) if err != nil { - return types.AssetInfo{}, err + return asset, oInfo, err } clientChainAddrLength := info.AddressLength assetAddr, ok := args[1].([]byte) if !ok || assetAddr == nil { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1]) + return asset, oInfo, err } if uint32(len(assetAddr)) < clientChainAddrLength { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength) + err = fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength) + return asset, oInfo, err } asset.Address = hexutil.Encode(assetAddr[:clientChainAddrLength]) decimal, ok := args[2].(uint8) if !ok { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "uint8", args[2]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "uint8", args[2]) + return asset, oInfo, err } 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]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 3, "*big.Int", args[3]) + return asset, oInfo, err } asset.TotalSupply = sdkmath.NewIntFromBigInt(tvlLimit) name, ok := args[4].(string) if !ok { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4]) + return asset, oInfo, err } if name == "" || len(name) > types.MaxChainTokenNameLength { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength) + err = fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength) + return asset, oInfo, err } asset.Name = name metaInfo, ok := args[5].(string) if !ok { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5]) + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5]) + return asset, oInfo, err } if metaInfo == "" || len(metaInfo) > types.MaxChainTokenMetaInfoLength { - return types.AssetInfo{}, fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength) + err = fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength) + return asset, oInfo, err } asset.MetaInfo = metaInfo - return asset, nil + oInfoStr, ok := args[6].(string) + if !ok { + err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 6, "string", args[6]) + return asset, oInfo, err + } + + if err = json.Unmarshal([]byte(oInfoStr), &oInfo); err != nil { + return asset, oInfo, err + } + if len(oInfo.Token.Name) == 0 || + len(oInfo.Token.Chain.Name) == 0 || + len(oInfo.Token.Decimal) == 0 { + err = errors.New(exocmn.ErrInvalidOracleInfo) + return asset, oInfo, err + } + + return asset, oInfo, err } func (p Precompile) ClientChainIDFromInputs(_ sdk.Context, args []interface{}) (uint32, error) { diff --git a/precompiles/common/error.go b/precompiles/common/error.go index e08b2b8ca..c8851b403 100644 --- a/precompiles/common/error.go +++ b/precompiles/common/error.go @@ -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, token.Chain.Name, token.Decimal" ) diff --git a/x/assets/types/expected_keepers.go b/x/assets/types/expected_keepers.go index 2ba61ab4f..d0a9e8f19 100644 --- a/x/assets/types/expected_keepers.go +++ b/x/assets/types/expected_keepers.go @@ -7,5 +7,5 @@ import ( type OracleKeeper interface { GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (oracletypes.Price, error) - RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, decimal, interval, contract, assetID string) error + RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *oracletypes.OracleInfo) error } diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index f4bc95b34..ed869e42a 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -31,14 +31,14 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { store.Set(types.ParamsKey, bz) } -func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, decimal, interval, contract, assetID string) error { +func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types.OracleInfo) error { p := k.GetParams(ctx) - if p.GetTokenIDFromAssetID(assetID) > 0 { - return fmt.Errorf("assetID exists:%s", assetID) + if p.GetTokenIDFromAssetID(oInfo.AssetID) > 0 { + return fmt.Errorf("assetID exists:%s", oInfo.AssetID) } chainID := uint64(0) for id, c := range p.Chains { - if c.Name == chain { + if c.Name == oInfo.Token.Chain.Name { chainID = uint64(id) break } @@ -46,19 +46,19 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, if chainID == 0 { // add new chain p.Chains = append(p.Chains, &types.Chain{ - Name: chain, - Desc: "registered through assets module", + Name: oInfo.Token.Chain.Name, + Desc: oInfo.Token.Chain.Desc, }) chainID = uint64(len(p.Chains) - 1) } - decimalInt, err := strconv.ParseInt(decimal, 10, 32) + decimalInt, err := strconv.ParseInt(oInfo.Token.Decimal, 10, 32) if err != nil { return err } if decimalInt < 0 { return fmt.Errorf("decimal can't be negative:%d", decimalInt) } - intervalInt, err := strconv.ParseUint(interval, 10, 64) + intervalInt, err := strconv.ParseUint(oInfo.Feeder.Interval, 10, 64) if err != nil { return err } @@ -69,8 +69,8 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, for _, t := range p.Tokens { // token exists, bind assetID for this token // it's possible for one price bonded with multiple assetID, like ETHUSDT from sepolia/mainnet - if t.Name == token && t.ChainID == chainID { - t.AssetID = strings.Join([]string{t.AssetID, assetID}, ",") + if t.Name == oInfo.Token.Name && t.ChainID == chainID { + t.AssetID = strings.Join([]string{t.AssetID, oInfo.AssetID}, ",") k.SetParams(ctx, p) // there should have been existing tokenFeeder running(currently we register tokens from assets-module and with infinite endBlock) return nil @@ -79,22 +79,32 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, chain, token, // add a new token p.Tokens = append(p.Tokens, &types.Token{ - Name: token, + Name: oInfo.Token.Name, ChainID: chainID, - ContractAddress: contract, + ContractAddress: oInfo.Token.Contract, Decimal: int32(decimalInt), Active: true, - AssetID: assetID, + AssetID: oInfo.AssetID, }) + + startInt, err := strconv.ParseUint(oInfo.Feeder.Start, 10, 64) + if err != nil { + return err + } + if startInt == 0 { + startInt = uint64(ctx.BlockHeight() + startAfterBlocks) + } + // set a tokenFeeder for the new token p.TokenFeeders = append(p.TokenFeeders, &types.TokenFeeder{ TokenID: uint64(len(p.Tokens) - 1), - // we support rule_1 for v1 + // we only support rule_1 for v1 RuleID: 1, StartRoundID: 1, - StartBaseBlock: uint64(ctx.BlockHeight() + startAfterBlocks), + StartBaseBlock: startInt, Interval: intervalInt, - EndBlock: 0, + // we don't end feeders for v1 + EndBlock: 0, }) k.SetParams(ctx, p) diff --git a/x/oracle/types/types.go b/x/oracle/types/types.go index 0ddb53bc8..b7b774040 100644 --- a/x/oracle/types/types.go +++ b/x/oracle/types/types.go @@ -6,6 +6,26 @@ import ( sdkmath "cosmossdk.io/math" ) +type OracleInfo struct { + Token struct { + Name string `json:"name"` + Chain struct { + Name string `json:"name"` + Desc string `json:"desc"` + } `json:"chain"` + Decimal string `json:"decimal"` + Contract string `json:"contract"` + AssetID string `json:"asset_id"` + } `json:"token"` + Feeder struct { + Start string `json:"start"` + End string `json:"end"` + Interval string `json:"interval"` + RuleID string `json:rule_id"` + } `json:"feeder"` + AssetID string `json:"asset_id"` +} + type Price struct { Value sdkmath.Int Decimal uint8 From 5d3d6252b30983479f72276694aaefcc3dd29775 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 13 Aug 2024 00:46:55 +0800 Subject: [PATCH 03/13] lint --- precompiles/assets/types.go | 3 +-- x/oracle/types/types.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index 8bf833993..08ba21ac0 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -1,12 +1,11 @@ package assets import ( + "encoding/json" "errors" "fmt" "math/big" - "encoding/json" - "github.com/ethereum/go-ethereum/common/hexutil" sdkmath "cosmossdk.io/math" diff --git a/x/oracle/types/types.go b/x/oracle/types/types.go index b7b774040..8824bef27 100644 --- a/x/oracle/types/types.go +++ b/x/oracle/types/types.go @@ -21,7 +21,7 @@ type OracleInfo struct { Start string `json:"start"` End string `json:"end"` Interval string `json:"interval"` - RuleID string `json:rule_id"` + RuleID string `json:"rule_id"` } `json:"feeder"` AssetID string `json:"asset_id"` } From ea61d794fffa24e4356b71eeb9df33d4528d4bbe Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 13 Aug 2024 11:54:26 +0800 Subject: [PATCH 04/13] keep necessary info for oracle register --- precompiles/assets/tx.go | 2 - precompiles/assets/types.go | 91 +++++++++++++++++++++---------------- precompiles/common/error.go | 2 +- x/oracle/keeper/params.go | 16 ++----- x/oracle/types/types.go | 21 +++++---- 5 files changed, 70 insertions(+), 62 deletions(-) diff --git a/precompiles/assets/tx.go b/precompiles/assets/tx.go index 6531209a1..60535b522 100644 --- a/precompiles/assets/tx.go +++ b/precompiles/assets/tx.go @@ -24,8 +24,6 @@ const ( MethodIsRegisteredClientChain = "isRegisteredClientChain" ) -// var oracleInfoMatcher = regexp.MustCompile(`oracle_info:{chain:(.+),\s*token:(.+),\s*decimal:(\d+)(,\s*interval:(.+))?(,\s*contract:(.+))?}`) - // DepositOrWithdraw deposit and withdraw the client chain assets for the staker, // that will change the state in assets module. func (p Precompile) DepositOrWithdraw( diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index 08ba21ac0..c715e860c 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -1,10 +1,11 @@ package assets import ( - "encoding/json" "errors" "fmt" "math/big" + "regexp" + "strings" "github.com/ethereum/go-ethereum/common/hexutil" @@ -17,6 +18,10 @@ import ( cmn "github.com/evmos/evmos/v14/precompiles/common" ) +// oracleInfo: '[tokenName],[chainName],[tokenDecimal](,[interval],[contract](,[ChainDesc:{...}],[TokenDesc:{...}]))' +var tokenDescMatcher = regexp.MustCompile(`TokenDesc:{(.+?)}`) +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 { @@ -111,88 +116,96 @@ func (p Precompile) ClientChainInfoFromInputs(_ sdk.Context, args []interface{}) return &clientChain, nil } -func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (asset types.AssetInfo, oInfo oracletypes.OracleInfo, err 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 { - err = fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) } + asset := types.AssetInfo{} + oracleInfo := types.OracleInfo{} + clientChainID, ok := args[0].(uint32) if !ok { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0]) - return asset, oInfo, err + 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 asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, err } clientChainAddrLength := info.AddressLength assetAddr, ok := args[1].([]byte) if !ok || assetAddr == nil { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1]) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1]) } if uint32(len(assetAddr)) < clientChainAddrLength { - err = fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength) - return asset, oInfo, err + 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 { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "uint8", args[2]) - return asset, oInfo, err + 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) { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 3, "*big.Int", args[3]) - return asset, oInfo, err + 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 { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4]) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 4, "string", args[4]) } if name == "" || len(name) > types.MaxChainTokenNameLength { - err = fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrInvalidNameLength, name, len(name), types.MaxChainTokenNameLength) } asset.Name = name metaInfo, ok := args[5].(string) if !ok { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5]) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrContractInputParaOrType, 5, "string", args[5]) } if metaInfo == "" || len(metaInfo) > types.MaxChainTokenMetaInfoLength { - err = fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength) - return asset, oInfo, err + return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metaInfo, len(metaInfo), types.MaxChainTokenMetaInfoLength) } asset.MetaInfo = metaInfo - oInfoStr, ok := args[6].(string) + oracleInfoStr, ok := args[6].(string) if !ok { - err = fmt.Errorf(exocmn.ErrContractInputParaOrType, 6, "string", args[6]) - return asset, oInfo, err - } - - if err = json.Unmarshal([]byte(oInfoStr), &oInfo); err != nil { - return asset, oInfo, err - } - if len(oInfo.Token.Name) == 0 || - len(oInfo.Token.Chain.Name) == 0 || - len(oInfo.Token.Decimal) == 0 { - err = errors.New(exocmn.ErrInvalidOracleInfo) - return asset, oInfo, err - } - - return asset, oInfo, err + 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) + } + + return asset, oracleInfo, nil } func (p Precompile) ClientChainIDFromInputs(_ sdk.Context, args []interface{}) (uint32, error) { diff --git a/precompiles/common/error.go b/precompiles/common/error.go index c8851b403..1e81e2f75 100644 --- a/precompiles/common/error.go +++ b/precompiles/common/error.go @@ -16,5 +16,5 @@ const ( ErrInvalidEVMAddr = "the address is an invalid EVM address, addr:%s" - ErrInvalidOracleInfo = "oracle info is invalid, need at least three fields not empty: token.Name, token.Chain.Name, token.Decimal" + ErrInvalidOracleInfo = "oracle info is invalid, need at least three fields not empty: token.Name, Chain.Name, token.Decimal" ) diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index ed869e42a..f0eab0c76 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -38,7 +38,7 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. } chainID := uint64(0) for id, c := range p.Chains { - if c.Name == oInfo.Token.Chain.Name { + if c.Name == oInfo.Chain.Name { chainID = uint64(id) break } @@ -46,8 +46,8 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. if chainID == 0 { // add new chain p.Chains = append(p.Chains, &types.Chain{ - Name: oInfo.Token.Chain.Name, - Desc: oInfo.Token.Chain.Desc, + Name: oInfo.Chain.Name, + Desc: oInfo.Chain.Desc, }) chainID = uint64(len(p.Chains) - 1) } @@ -87,21 +87,13 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. AssetID: oInfo.AssetID, }) - startInt, err := strconv.ParseUint(oInfo.Feeder.Start, 10, 64) - if err != nil { - return err - } - if startInt == 0 { - startInt = uint64(ctx.BlockHeight() + startAfterBlocks) - } - // set a tokenFeeder for the new token p.TokenFeeders = append(p.TokenFeeders, &types.TokenFeeder{ TokenID: uint64(len(p.Tokens) - 1), // we only support rule_1 for v1 RuleID: 1, StartRoundID: 1, - StartBaseBlock: startInt, + StartBaseBlock: uint64(ctx.BlockHeight() + startAfterBlocks), Interval: intervalInt, // we don't end feeders for v1 EndBlock: 0, diff --git a/x/oracle/types/types.go b/x/oracle/types/types.go index 8824bef27..18135f518 100644 --- a/x/oracle/types/types.go +++ b/x/oracle/types/types.go @@ -7,21 +7,26 @@ import ( ) type OracleInfo struct { + Chain struct { + Name string + Desc string + } Token struct { - Name string `json:"name"` - Chain struct { - Name string `json:"name"` - Desc string `json:"desc"` - } `json:"chain"` + Name string `json:"name"` + Desc string + // Chain struct { + // Name string `json:"name"` + // Desc string `json:"desc"` + // } `json:"chain"` Decimal string `json:"decimal"` Contract string `json:"contract"` AssetID string `json:"asset_id"` } `json:"token"` Feeder struct { - Start string `json:"start"` - End string `json:"end"` + // Start string `json:"start"` + // End string `json:"end"` Interval string `json:"interval"` - RuleID string `json:"rule_id"` + // RuleID string `json:"rule_id"` } `json:"feeder"` AssetID string `json:"asset_id"` } From 0a30dd6582d1d510e090297b9004511544e617bb Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 13 Aug 2024 12:25:53 +0800 Subject: [PATCH 05/13] fix:typo --- precompiles/assets/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index c715e860c..f47500fdb 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -122,7 +122,7 @@ func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types. return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) } asset := types.AssetInfo{} - oracleInfo := types.OracleInfo{} + oracleInfo := oracletypes.OracleInfo{} clientChainID, ok := args[0].(uint32) if !ok { From cb54e7c384ca0f74d95a99aeb30a7bee0206251d Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 13 Aug 2024 22:34:11 +0800 Subject: [PATCH 06/13] lint --- precompiles/assets/types.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index f47500fdb..bc016947c 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -19,8 +19,10 @@ import ( ) // oracleInfo: '[tokenName],[chainName],[tokenDecimal](,[interval],[contract](,[ChainDesc:{...}],[TokenDesc:{...}]))' -var tokenDescMatcher = regexp.MustCompile(`TokenDesc:{(.+?)}`) -var chainDescMatcher = regexp.MustCompile(`ChainDesc:{(.+?)}`) +var ( + tokenDescMatcher = regexp.MustCompile(`TokenDesc:{(.+?)}`) + chainDescMatcher = regexp.MustCompile(`ChainDesc:{(.+?)}`) +) func (p Precompile) DepositWithdrawParamsFromInputs(ctx sdk.Context, args []interface{}) (*assetskeeper.DepositWithdrawParams, error) { inputsLen := len(p.ABI.Methods[MethodDepositTo].Inputs) From 28fde006618b67318b941911220c6898e5a717d8 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Wed, 14 Aug 2024 18:19:43 +0800 Subject: [PATCH 07/13] fix:check empty interval field --- precompiles/assets/types.go | 1 + x/oracle/keeper/params.go | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index bc016947c..393f6d2ca 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -193,6 +193,7 @@ func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types. if len(chainDesc) == 2 { oracleInfo.Chain.Desc = chainDesc[1] } + fallthrough case l >= 5: oracleInfo.Token.Contract = parsed[4] fallthrough diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index f0eab0c76..3131b9f76 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -16,7 +16,7 @@ const ( func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) // return types.NewParams() + bz := store.Get(types.ParamsKey) if bz != nil { k.cdc.MustUnmarshal(bz, ¶ms) } @@ -55,12 +55,12 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. if err != nil { return err } - if decimalInt < 0 { - return fmt.Errorf("decimal can't be negative:%d", decimalInt) - } - intervalInt, err := strconv.ParseUint(oInfo.Feeder.Interval, 10, 64) - if err != nil { - return err + intervalInt := uint64(0) + if len(oInfo.Feeder.Interval) > 0 { + intervalInt, err = strconv.ParseUint(oInfo.Feeder.Interval, 10, 64) + if err != nil { + return err + } } if intervalInt == 0 { intervalInt = defaultInterval From eea41e6a17f8ab61df695950d86b6163af65537b Mon Sep 17 00:00:00 2001 From: leonz789 Date: Thu, 15 Aug 2024 18:36:23 +0800 Subject: [PATCH 08/13] fix: set aggregator context when params updated by assetsmodule --- x/oracle/keeper/params.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index 3131b9f76..8755b82de 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/ExocoreNetwork/exocore/x/oracle/keeper/cache" "github.com/ExocoreNetwork/exocore/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -100,5 +101,7 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. }) k.SetParams(ctx, p) + _ = GetAggregatorContext(ctx, k) + cs.AddCache(cache.ItemP(p)) return nil } From 52db44161536508e242630adb059809b5a0149c1 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Fri, 16 Aug 2024 01:50:08 +0800 Subject: [PATCH 09/13] lint --- app/ante/cosmos/authz.go | 3 +-- precompiles/avs/types.go | 2 +- testutil/abci.go | 5 ++--- x/dogfood/types/genesis.go | 2 +- x/oracle/keeper/aggregator/context.go | 4 ++-- x/oracle/types/params.go | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/ante/cosmos/authz.go b/app/ante/cosmos/authz.go index 808c3e900..b15b64fdd 100644 --- a/app/ante/cosmos/authz.go +++ b/app/ante/cosmos/authz.go @@ -3,7 +3,6 @@ package cosmos import ( "fmt" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" @@ -28,7 +27,7 @@ func NewAuthzLimiterDecorator(disabledMsgTypes ...string) AuthzLimiterDecorator func (ald AuthzLimiterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { if err := ald.checkDisabledMsgs(tx.GetMsgs(), false, 1); err != nil { - return ctx, errorsmod.Wrapf(errortypes.ErrUnauthorized, err.Error()) + return ctx, errortypes.ErrUnauthorized.Wrap(err.Error()) } return next(ctx, tx, simulate) } diff --git a/precompiles/avs/types.go b/precompiles/avs/types.go index 32ac287f6..dab2cb2c9 100644 --- a/precompiles/avs/types.go +++ b/precompiles/avs/types.go @@ -64,7 +64,7 @@ func (p Precompile) GetAVSParamsFromInputs(_ sdk.Context, args []interface{}) (* // string, since it is the address_id representation assetID, ok := args[6].([]string) - if !ok || assetID == nil || len(assetID) == 0 { + if !ok || len(assetID) == 0 { return nil, fmt.Errorf(exocmn.ErrContractInputParaOrType, 6, "[]string", assetID) } avsParams.AssetID = assetID diff --git a/testutil/abci.go b/testutil/abci.go index 368446e31..7c8bccbf8 100644 --- a/testutil/abci.go +++ b/testutil/abci.go @@ -3,7 +3,6 @@ package testutil import ( "time" - errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -191,7 +190,7 @@ func BroadcastTxBytes(app *app.ExocoreApp, txEncoder sdk.TxEncoder, tx sdk.Tx) ( req := abci.RequestDeliverTx{Tx: bz} res := app.BaseApp.DeliverTx(req) if res.Code != 0 { - return abci.ResponseDeliverTx{}, errorsmod.Wrapf(errortypes.ErrInvalidRequest, res.Log) + return abci.ResponseDeliverTx{}, errortypes.ErrInvalidRequest.Wrap(res.Log) } return res, nil @@ -238,7 +237,7 @@ func checkTxBytes(app *app.ExocoreApp, txEncoder sdk.TxEncoder, tx sdk.Tx) (abci req := abci.RequestCheckTx{Tx: bz} res := app.BaseApp.CheckTx(req) if res.Code != 0 { - return abci.ResponseCheckTx{}, errorsmod.Wrapf(errortypes.ErrInvalidRequest, res.Log) + return abci.ResponseCheckTx{}, errortypes.ErrInvalidRequest.Wrap(res.Log) } return res, nil diff --git a/x/dogfood/types/genesis.go b/x/dogfood/types/genesis.go index e857b501e..e6dba4e06 100644 --- a/x/dogfood/types/genesis.go +++ b/x/dogfood/types/genesis.go @@ -257,7 +257,7 @@ func (gs GenesisState) Validate() error { } if gs.LastTotalPower.IsNil() { - return errorsmod.Wrapf( + return errorsmod.Wrap( ErrInvalidGenesisData, "nil last total power", ) diff --git a/x/oracle/keeper/aggregator/context.go b/x/oracle/keeper/aggregator/context.go index d4b0f4de0..dc42f8288 100644 --- a/x/oracle/keeper/aggregator/context.go +++ b/x/oracle/keeper/aggregator/context.go @@ -90,12 +90,12 @@ func (agc *AggregatorContext) sanityCheck(msg *types.MsgCreatePrice) error { } // TODO: sanity check for price(no more than maxDetId count for each source, this should be take care in anteHandler) - if msg.Prices == nil || len(msg.Prices) == 0 { + if len(msg.Prices) == 0 { return errors.New("msg should provide at least one price") } for _, pSource := range msg.Prices { - if pSource.Prices == nil || len(pSource.Prices) == 0 || len(pSource.Prices) > int(common.MaxDetID) || !agc.params.IsValidSource(pSource.SourceID) { + if len(pSource.Prices) == 0 || len(pSource.Prices) > int(common.MaxDetID) || !agc.params.IsValidSource(pSource.SourceID) { return errors.New("source should be valid and provide at least one price") } // check with params is coressponding source is deteministic diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index 3ab771690..6cf63e0c2 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -529,7 +529,7 @@ func (p Params) CheckRules(feederID uint64, prices []*PriceSource) (bool, error) feeder := p.TokenFeeders[feederID] rule := p.Rules[feeder.RuleID] // specified sources set, v1 use this rule to set `chainlink` as official source - if rule.SourceIDs != nil && len(rule.SourceIDs) > 0 { + if len(rule.SourceIDs) > 0 { if len(rule.SourceIDs) != len(prices) { return false, errors.New("count prices should match rule") } From fc6fac1c4dad5156efcc0b62d8d0fe5dfcb5463c Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:42:05 +0000 Subject: [PATCH 10/13] refactor(IAssets): split into update and add token --- precompiles/assets/IAssets.sol | 28 +++++-- precompiles/assets/abi.json | 149 ++++++++++++++++++++------------- precompiles/assets/assets.go | 32 ++++--- precompiles/assets/tx.go | 81 ++++++++++++------ precompiles/assets/types.go | 48 ++++++++++- 5 files changed, 233 insertions(+), 105 deletions(-) diff --git a/precompiles/assets/IAssets.sol b/precompiles/assets/IAssets.sol index c3a0bdb0d..b5e118f9c 100644 --- a/precompiles/assets/IAssets.sol +++ b/precompiles/assets/IAssets.sol @@ -55,27 +55,41 @@ interface IAssets { string calldata signatureType ) external returns (bool success, bool updated); - /// @dev register or update token addresses to exocore + /// @dev register a token to allow deposits / staking, etc. /// @dev note that there is no way to delete a token. If a token is to be removed, /// the TVL limit should be set to 0. - /// @param clientChainID is the identifier of the token's home chain (LZ or otherwise) + /// @param clientChainId is the identifier of the token's home chain (LZ or otherwise) /// @param token is the address of the token on the home chain /// @param decimals is the number of decimals of the token /// @param tvlLimit is the number of tokens that can be deposited in the system. Set to /// maxSupply if there is no limit /// @param name is the name of the token /// @param metaData is the arbitrary metadata of the token + /// @param oracleInfo is the oracle information of the token /// @return success if the token registration is successful - /// @return updated whether the token was added or updated - function registerOrUpdateTokens( - uint32 clientChainID, + function registerToken( + uint32 clientChainId, bytes calldata token, uint8 decimals, uint256 tvlLimit, string calldata name, string calldata metaData, - string calldata oracleInfo - ) external returns (bool success, bool updated); + string calldata oracleInfo + ) external returns (bool success); + + /// @dev update a token to allow deposits / staking, etc. + /// @param clientChainId is the identifier of the token's home chain (LZ or otherwise) + /// @param token is the address of the token on the home chain + /// @param tvlLimit is the number of tokens that can be deposited in the system. Set to + /// maxSupply if there is no limit + /// @param metaData is the arbitrary metadata of the token + /// @return success if the token update is successful + /// @dev The token must previously be registered before updating + /// @dev Pass a tvlLimit of 0 to disable any further deposits + /// @dev Pass en empty metadata to keep the existing metadata + function updateToken(uint32 clientChainId, bytes calldata token, uint256 tvlLimit, string calldata metaData) + external + returns (bool success); /// QUERIES /// @dev Returns the chain indices of the client chains. diff --git a/precompiles/assets/abi.json b/precompiles/assets/abi.json index a1d04ec2f..92065a443 100644 --- a/precompiles/assets/abi.json +++ b/precompiles/assets/abi.json @@ -126,10 +126,10 @@ }, { "type": "function", - "name": "registerOrUpdateTokens", + "name": "registerToken", "inputs": [ { - "name": "clientChainID", + "name": "clientChainId", "type": "uint32", "internalType": "uint32" }, @@ -156,65 +156,94 @@ { "name": "metaData", "type": "string", - "internalType": "string" - }, - { - "name": "oracleInfo", - "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" + } + ], + "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": "updateToken", + "inputs": [ + { + "name": "clientChainId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "token", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "tvlLimit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "metaData", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "success", + "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" } ] diff --git a/precompiles/assets/assets.go b/precompiles/assets/assets.go index 02196e634..cb8b10f01 100644 --- a/precompiles/assets/assets.go +++ b/precompiles/assets/assets.go @@ -102,27 +102,33 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ bz, err = p.RegisterOrUpdateClientChain(ctx, contract, method, args) if err != nil { ctx.Logger().Error("internal error when calling assets precompile", "module", "assets precompile", "method", method.Name, "err", err) - // for failed cases we expect it returns bool value instead of error - // this is a workaround because the error returned by precompile can not be caught in EVM - // see https://github.com/ExocoreNetwork/exocore/issues/70 - // TODO: we should figure out root cause and fix this issue to make precompiles work normally - bz, err = method.Outputs.Pack(false) // Adjust based on actual needs + bz, err = method.Outputs.Pack(false, false) } - case MethodRegisterOrUpdateTokens: - bz, err = p.RegisterOrUpdateTokens(ctx, contract, method, args) + case MethodRegisterToken: + bz, err = p.RegisterToken(ctx, contract, method, args) if err != nil { ctx.Logger().Error("internal error when calling assets precompile", "module", "assets precompile", "method", method.Name, "err", err) - // for failed cases we expect it returns bool value instead of error - // this is a workaround because the error returned by precompile can not be caught in EVM - // see https://github.com/ExocoreNetwork/exocore/issues/70 - // TODO: we should figure out root cause and fix this issue to make precompiles work normally - bz, err = method.Outputs.Pack(false) // Adjust based on actual needs + bz, err = method.Outputs.Pack(false) + } + case MethodUpdateToken: + bz, err = p.UpdateToken(ctx, contract, method, args) + if err != nil { + ctx.Logger().Error("internal error when calling assets precompile", "module", "assets precompile", "method", method.Name, "err", err) + bz, err = method.Outputs.Pack(false) } // queries case MethodGetClientChains: bz, err = p.GetClientChains(ctx, method, args) + if err != nil { + ctx.Logger().Error("internal error when calling assets precompile", "module", "assets precompile", "method", method.Name, "err", err) + bz, err = method.Outputs.Pack(false, []uint32{}) + } case MethodIsRegisteredClientChain: bz, err = p.IsRegisteredClientChain(ctx, method, args) + if err != nil { + ctx.Logger().Error("internal error when calling assets precompile", "module", "assets precompile", "method", method.Name, "err", err) + bz, err = method.Outputs.Pack(false, false) + } default: return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) } @@ -144,7 +150,7 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // IsTransaction checks if the given methodID corresponds to a transaction or query. func (Precompile) IsTransaction(methodID string) bool { switch methodID { - case MethodDepositTo, MethodWithdraw, MethodRegisterOrUpdateClientChain, MethodRegisterOrUpdateTokens: + case MethodDepositTo, MethodWithdraw, MethodRegisterOrUpdateClientChain, MethodRegisterToken, MethodUpdateToken: return true case MethodGetClientChains, MethodIsRegisteredClientChain: return false diff --git a/precompiles/assets/tx.go b/precompiles/assets/tx.go index 60535b522..ac70d2c3d 100644 --- a/precompiles/assets/tx.go +++ b/precompiles/assets/tx.go @@ -20,7 +20,8 @@ const ( MethodWithdraw = "withdrawPrincipal" MethodGetClientChains = "getClientChains" MethodRegisterOrUpdateClientChain = "registerOrUpdateClientChain" - MethodRegisterOrUpdateTokens = "registerOrUpdateTokens" + MethodRegisterToken = "registerToken" + MethodUpdateToken = "updateToken" MethodIsRegisteredClientChain = "isRegisteredClientChain" ) @@ -116,7 +117,7 @@ func (p Precompile) RegisterOrUpdateClientChain( return method.Outputs.Pack(true, updated) } -func (p Precompile) RegisterOrUpdateTokens( +func (p Precompile) RegisterToken( ctx sdk.Context, contract *vm.Contract, method *abi.Method, @@ -134,29 +135,20 @@ func (p Precompile) RegisterOrUpdateTokens( return nil, err } - // the price feed must exist _, assetID := assetstypes.GetStakeIDAndAssetIDFromStr(asset.LayerZeroChainID, "", asset.Address) 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 - } + + if p.assetsKeeper.IsStakingAsset(ctx, assetID) { + return nil, fmt.Errorf("asset %s already exists", assetID) + } + + stakingAsset := &assetstypes.StakingAssetInfo{ + AssetBasicInfo: &asset, + StakingTotalAmount: sdkmath.NewInt(0), + } + + if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, &oInfo); err != nil { + return nil, err } // this is where the magic happens @@ -164,7 +156,48 @@ func (p Precompile) RegisterOrUpdateTokens( return nil, err } - return method.Outputs.Pack(true, updated) + return method.Outputs.Pack(true) +} + +func (p Precompile) UpdateToken( + ctx sdk.Context, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + // the caller must be the ExocoreGateway contract + err := p.assetsKeeper.CheckExocoreGatewayAddr(ctx, contract.CallerAddress) + if err != nil { + return nil, fmt.Errorf(exocmn.ErrContractCaller, err.Error()) + } + + // parse inputs + clientChainID, hexAssetAddr, tvlLimit, metadata, err := p.UpdateTokenFromInputs(ctx, args) + if err != nil { + return nil, err + } + + // check that the asset being updated actually exists + _, assetID := assetstypes.GetStakeIDAndAssetIDFromStr(uint64(clientChainID), "", hexAssetAddr) + assetInfo, err := p.assetsKeeper.GetStakingAssetInfo(ctx, assetID) + if err != nil { + // fails if asset does not exist with ErrNoClientChainAssetKey + return nil, err + } + + // finally, execute the update + if len(metadata) > 0 { + // if metadata is not empty, update it + assetInfo.AssetBasicInfo.MetaInfo = metadata + } + // always update TVL, since a value of 0 is used to reject deposits + assetInfo.AssetBasicInfo.TotalSupply = tvlLimit + + if err := p.assetsKeeper.SetStakingAssetInfo(ctx, assetInfo); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) } func (p Precompile) IsRegisteredClientChain( diff --git a/precompiles/assets/types.go b/precompiles/assets/types.go index 393f6d2ca..b67ed6dbb 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -119,7 +119,7 @@ func (p Precompile) ClientChainInfoFromInputs(_ sdk.Context, args []interface{}) } func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, oracletypes.OracleInfo, error) { - inputsLen := len(p.ABI.Methods[MethodRegisterOrUpdateTokens].Inputs) + inputsLen := len(p.ABI.Methods[MethodRegisterToken].Inputs) if len(args) != inputsLen { return types.AssetInfo{}, oracletypes.OracleInfo{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) } @@ -211,6 +211,52 @@ func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types. return asset, oracleInfo, nil } +func (p Precompile) UpdateTokenFromInputs( + ctx sdk.Context, args []interface{}, +) (clientChainID uint32, hexAssetAddr string, tvlLimit sdkmath.Int, metadata string, err error) { + inputsLen := len(p.ABI.Methods[MethodUpdateToken].Inputs) + if len(args) != inputsLen { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(cmn.ErrInvalidNumberOfArgs, inputsLen, len(args)) + } + + clientChainID, ok := args[0].(uint32) + if !ok { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrContractInputParaOrType, 0, "uint32", args[0]) + } + + info, err := p.assetsKeeper.GetClientChainInfoByIndex(ctx, uint64(clientChainID)) + if err != nil { + return 0, "", sdkmath.NewInt(0), "", err + } + clientChainAddrLength := info.AddressLength + assetAddr, ok := args[1].([]byte) + if !ok || assetAddr == nil { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrContractInputParaOrType, 1, "[]byte", args[1]) + } + if uint32(len(assetAddr)) < clientChainAddrLength { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrInvalidAddrLength, len(assetAddr), clientChainAddrLength) + } + hexAssetAddr = hexutil.Encode(assetAddr[:clientChainAddrLength]) + + tvlLimitLocal, ok := args[2].(*big.Int) + // tvlLimit can be 0 to block the token deposits + if !ok || tvlLimitLocal == nil { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrContractInputParaOrType, 2, "*big.Int", args[2]) + } + tvlLimit = sdkmath.NewIntFromBigInt(tvlLimitLocal) + + metadata, ok = args[3].(string) + if !ok { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrContractInputParaOrType, 3, "string", args[3]) + } + // metadata can be blank here to indicate no update necessary + if len(metadata) > types.MaxChainTokenMetaInfoLength { + return 0, "", sdkmath.NewInt(0), "", fmt.Errorf(exocmn.ErrInvalidMetaInfoLength, metadata, len(metadata), types.MaxChainTokenMetaInfoLength) + } + + return clientChainID, hexAssetAddr, tvlLimit, metadata, nil +} + func (p Precompile) ClientChainIDFromInputs(_ sdk.Context, args []interface{}) (uint32, error) { inputsLen := len(p.ABI.Methods[MethodIsRegisteredClientChain].Inputs) if len(args) != inputsLen { From 65e2811076458d3a19e97d908067652fdfeedf22 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:13:58 +0000 Subject: [PATCH 11/13] chore: golang lint --- app/ante/cosmos/txsize_gas.go | 5 ++--- precompiles/testutil/logs.go | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/ante/cosmos/txsize_gas.go b/app/ante/cosmos/txsize_gas.go index 7ccd8d2bc..f70162058 100644 --- a/app/ante/cosmos/txsize_gas.go +++ b/app/ante/cosmos/txsize_gas.go @@ -1,7 +1,6 @@ package cosmos import ( - "github.com/ExocoreNetwork/exocore/app/ante/utils" anteutils "github.com/ExocoreNetwork/exocore/app/ante/utils" "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" @@ -40,8 +39,8 @@ func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim // Skip gas consumption if tx is an OracleCreatePriceTx if anteutils.IsOracleCreatePriceTx(tx) { - if len(ctx.TxBytes()) > utils.TxSizeLimit { - return ctx, sdkerrors.ErrTxTooLarge.Wrapf("oracle create-price tx has exceeds size limit, limit:%d, got:%d", utils.TxSizeLimit, len(ctx.TxBytes())) + if len(ctx.TxBytes()) > anteutils.TxSizeLimit { + return ctx, sdkerrors.ErrTxTooLarge.Wrapf("oracle create-price tx has exceeds size limit, limit:%d, got:%d", anteutils.TxSizeLimit, len(ctx.TxBytes())) } return next(ctx, tx, simulate) } diff --git a/precompiles/testutil/logs.go b/precompiles/testutil/logs.go index 1f587f80a..d31de57cf 100644 --- a/precompiles/testutil/logs.go +++ b/precompiles/testutil/logs.go @@ -37,7 +37,6 @@ func CheckLogs(logArgs LogCheckArgs) error { int64(float64(logArgs.Res.GasUsed)/float64(logArgs.Res.GasWanted)*100), ) } - // nolint if err := CheckVMError(logArgs.Res, logArgs.ErrContains); err != nil { return err } From 3c2f9bb5271dbd7ff3779e455bb8099c99e7d84f Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:57:26 +0000 Subject: [PATCH 12/13] chore: allow gitleaks --- app/ante/cosmos/txsize_gas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ante/cosmos/txsize_gas.go b/app/ante/cosmos/txsize_gas.go index f70162058..88333906a 100644 --- a/app/ante/cosmos/txsize_gas.go +++ b/app/ante/cosmos/txsize_gas.go @@ -70,7 +70,7 @@ func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim // use placeholder simSecp256k1Pubkey if sig is nil if acc == nil || acc.GetPubKey() == nil { - pubkey = simSecp256k1Pubkey + pubkey = simSecp256k1Pubkey // gitleaks:allow } else { pubkey = acc.GetPubKey() } From 9cc32eeb6e66747c91fb9cd1a5b4b1a5cbe151bb Mon Sep 17 00:00:00 2001 From: leonz789 Date: Thu, 5 Sep 2024 10:35:59 +0800 Subject: [PATCH 13/13] fix: skip cache update for directly EVM call --- x/oracle/keeper/params.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index 8755b82de..ce152dcbe 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -101,7 +101,11 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. }) k.SetParams(ctx, p) - _ = GetAggregatorContext(ctx, k) - cs.AddCache(cache.ItemP(p)) + // skip cache update if this is not deliverTx + // for normal cosmostx, checkTx will skip actual message exucution and do anteHandler only, but from ethc.callContract the message will be executed without anteHandler check as checkTx mode. + if !ctx.IsCheckTx() { + _ = GetAggregatorContext(ctx, k) + cs.AddCache(cache.ItemP(p)) + } return nil }