diff --git a/app/ante/cosmos/txsize_gas.go b/app/ante/cosmos/txsize_gas.go index 846c9e502..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 //gitleaks:allow + pubkey = simSecp256k1Pubkey // gitleaks:allow } else { pubkey = acc.GetPubKey() } diff --git a/precompiles/assets/IAssets.sol b/precompiles/assets/IAssets.sol index 655803410..b5e118f9c 100644 --- a/precompiles/assets/IAssets.sol +++ b/precompiles/assets/IAssets.sol @@ -55,26 +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 - ) external returns (bool success, bool updated); + string calldata metaData, + 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 b4d0dde0c..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" }, @@ -157,6 +157,11 @@ "name": "metaData", "type": "string", "internalType": "string" + }, + { + "name": "oracleInfo", + "type": "string", + "internalType": "string" } ], "outputs": [ @@ -164,9 +169,38 @@ "name": "success", "type": "bool", "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateToken", + "inputs": [ + { + "name": "clientChainId", + "type": "uint32", + "internalType": "uint32" }, { - "name": "updated", + "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" } diff --git a/precompiles/assets/assets.go b/precompiles/assets/assets.go index fcbd55373..cb8b10f01 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 { @@ -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, 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, 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 e76e61ec1..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, @@ -129,27 +130,74 @@ 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 + + if p.assetsKeeper.IsStakingAsset(ctx, assetID) { + return nil, fmt.Errorf("asset %s already exists", assetID) } - updated := p.assetsKeeper.IsStakingAsset(ctx, assetID) - // this is where the magic happens - if err := p.assetsKeeper.SetStakingAssetInfo(ctx, &assetstypes.StakingAssetInfo{ + stakingAsset := &assetstypes.StakingAssetInfo{ AssetBasicInfo: &asset, StakingTotalAmount: sdkmath.NewInt(0), - }); err != nil { + } + + if err := p.assetsKeeper.RegisterNewTokenAndSetTokenFeeder(ctx, &oInfo); err != nil { return nil, err } - return method.Outputs.Pack(true, updated) + // this is where the magic happens + if err := p.assetsKeeper.SetStakingAssetInfo(ctx, stakingAsset); err != nil { + return nil, err + } + + 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 3b825d30a..44a923038 100644 --- a/precompiles/assets/types.go +++ b/precompiles/assets/types.go @@ -1,8 +1,11 @@ package assets import ( + "errors" "fmt" "math/big" + "regexp" + "strings" "github.com/ethereum/go-ethereum/common/hexutil" @@ -10,10 +13,17 @@ import ( 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:{(.+?)}`) + 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,65 +121,145 @@ func (p Precompile) ClientChainInfoFromInputs(_ sdk.Context, args []interface{}) return &clientChain, nil } -func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, error) { - inputsLen := len(p.ABI.Methods[MethodRegisterOrUpdateTokens].Inputs) +func (p Precompile) TokenFromInputs(ctx sdk.Context, args []interface{}) (types.AssetInfo, oracletypes.OracleInfo, error) { + inputsLen := len(p.ABI.Methods[MethodRegisterToken].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]) } // #nosec G115 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]) } // #nosec G115 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] + } + fallthrough + 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) 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) { 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/precompiles/common/error.go b/precompiles/common/error.go index e08b2b8ca..1e81e2f75 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, Chain.Name, token.Decimal" ) diff --git a/x/assets/types/expected_keepers.go b/x/assets/types/expected_keepers.go index c1fe65043..d0a9e8f19 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, oInfo *oracletypes.OracleInfo) error } 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/params.go b/x/oracle/keeper/params.go index 5dcd5402f..ce152dcbe 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -1,13 +1,23 @@ package keeper import ( + "fmt" + "strconv" + "strings" + + "github.com/ExocoreNetwork/exocore/x/oracle/keeper/cache" "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() + bz := store.Get(types.ParamsKey) if bz != nil { k.cdc.MustUnmarshal(bz, ¶ms) } @@ -21,3 +31,81 @@ 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, oInfo *types.OracleInfo) error { + p := k.GetParams(ctx) + 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 == oInfo.Chain.Name { + chainID = uint64(id) + break + } + } + if chainID == 0 { + // add new chain + p.Chains = append(p.Chains, &types.Chain{ + Name: oInfo.Chain.Name, + Desc: oInfo.Chain.Desc, + }) + chainID = uint64(len(p.Chains) - 1) + } + decimalInt, err := strconv.ParseInt(oInfo.Token.Decimal, 10, 32) + 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 + } + + 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 == 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 + } + } + + // add a new token + p.Tokens = append(p.Tokens, &types.Token{ + Name: oInfo.Token.Name, + ChainID: chainID, + ContractAddress: oInfo.Token.Contract, + Decimal: int32(decimalInt), + Active: true, + AssetID: oInfo.AssetID, + }) + + // 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: uint64(ctx.BlockHeight() + startAfterBlocks), + Interval: intervalInt, + // we don't end feeders for v1 + EndBlock: 0, + }) + + k.SetParams(ctx, 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 +} diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index fc29e449f..a5b230256 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -486,8 +486,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 diff --git a/x/oracle/types/types.go b/x/oracle/types/types.go index 0ddb53bc8..18135f518 100644 --- a/x/oracle/types/types.go +++ b/x/oracle/types/types.go @@ -6,6 +6,31 @@ import ( sdkmath "cosmossdk.io/math" ) +type OracleInfo struct { + Chain struct { + Name string + Desc string + } + Token struct { + 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"` + Interval string `json:"interval"` + // RuleID string `json:"rule_id"` + } `json:"feeder"` + AssetID string `json:"asset_id"` +} + type Price struct { Value sdkmath.Int Decimal uint8