Skip to content

Commit

Permalink
feat(oracle): register new token and set up feeder through assets-mod…
Browse files Browse the repository at this point in the history
…ule (#159)

* feat(oracle): register new token and set up tokenfeeder through assets-module

* set oracle info as a new field instead of embedded into metaInfo

* lint

* keep necessary info for oracle register

* fix:typo

* lint

* fix:check empty interval field

* fix: set aggregator context when params updated by assetsmodule

* lint

* refactor(IAssets): split into update and add token

* chore: golang lint

* chore: allow gitleaks

* fix: skip cache update for directly EVM call

---------

Co-authored-by: MaxMustermann2 <[email protected]>
Co-authored-by: cloud8little <[email protected]>
  • Loading branch information
3 people authored Sep 5, 2024
1 parent 423b9f7 commit 3aee307
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 55 deletions.
2 changes: 1 addition & 1 deletion app/ante/cosmos/txsize_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
29 changes: 22 additions & 7 deletions precompiles/assets/IAssets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
40 changes: 37 additions & 3 deletions precompiles/assets/abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@
},
{
"type": "function",
"name": "registerOrUpdateTokens",
"name": "registerToken",
"inputs": [
{
"name": "clientChainID",
"name": "clientChainId",
"type": "uint32",
"internalType": "uint32"
},
Expand Down Expand Up @@ -157,16 +157,50 @@
"name": "metaData",
"type": "string",
"internalType": "string"
},
{
"name": "oracleInfo",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"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"
}
Expand Down
34 changes: 20 additions & 14 deletions 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 All @@ -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)
}
Expand All @@ -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
Expand Down
70 changes: 59 additions & 11 deletions precompiles/assets/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const (
MethodWithdraw = "withdrawPrincipal"
MethodGetClientChains = "getClientChains"
MethodRegisterOrUpdateClientChain = "registerOrUpdateClientChain"
MethodRegisterOrUpdateTokens = "registerOrUpdateTokens"
MethodRegisterToken = "registerToken"
MethodUpdateToken = "updateToken"
MethodIsRegisteredClientChain = "isRegisteredClientChain"
)

Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down
Loading

0 comments on commit 3aee307

Please sign in to comment.