Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(x/callback): Implement the storage of callback #500

Merged
merged 10 commits into from
Nov 22, 2023
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ require (
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/prometheus/client_golang v1.16.0
github.com/rakyll/statik v0.1.7
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.7.0
Expand Down Expand Up @@ -152,6 +151,7 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rakyll/statik v0.1.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/rs/cors v1.8.3 // indirect
Expand Down
2 changes: 1 addition & 1 deletion proto/archway/callback/v1/callback.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ message Callback {
// job_id is an identifier the callback requestor can pass in to identify the callback when it happens.
uint64 job_id = 2;
// callback_height is the height at which the callback is executed.
uint64 callback_height = 3;
int64 callback_height = 3;
// fee_split is the breakdown of the fees paid by the contract to reserve the callback
CallbackFeesFeeSplit fee_split = 4;
// reserved_by is the address which reserved the callback (bech32 encoded).
Expand Down
4 changes: 2 additions & 2 deletions proto/archway/callback/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ message QueryParamsResponse {
// QueryEstimateCallbackFeesRequest is the request for Query.EstimateCallbackFees.
message QueryEstimateCallbackFeesRequest{
// block_height is the height at which to estimate the callback fees
uint64 block_height = 1;
int64 block_height = 1;
}

// QueryEstimateCallbackFeesResponse is the response for Query.EstimateCallbackFees.
Expand All @@ -50,7 +50,7 @@ message QueryEstimateCallbackFeesResponse{
// QueryCallbacksRequest is the request for Query.Callbacks.
message QueryCallbacksRequest{
// block_height is the height at which to query the callbacks
uint64 block_height = 1;
int64 block_height = 1;
}

// QueryCallbacksResponse is the response for Query.Callbacks.
Expand Down
4 changes: 2 additions & 2 deletions proto/archway/callback/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ message MsgRequestCallback {
// job_id is an identifier the callback requestor can pass in to identify the callback when it happens
uint64 job_id = 3;
// callback_height is the height at which the callback is executed.
uint64 callback_height = 4;
int64 callback_height = 4;
// fees is the amount of fees being paid to register the contract
repeated cosmos.base.v1beta1.Coin fees = 5 [ (gogoproto.nullable) = false ];
}
Expand All @@ -62,7 +62,7 @@ message MsgCancelCallback{
// job_id is an identifier the callback requestor had passed during registration of the callback
uint64 job_id = 2;
// callback_height is the height at which the callback requestor had registered the callback
uint64 callback_height = 3;
int64 callback_height = 3;
}


Expand Down
89 changes: 89 additions & 0 deletions x/callback/keeper/callback.go
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
package keeper

import (
"cosmossdk.io/collections"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/archway-network/archway/x/callback/types"
)

// GetAllCallbacks lists all the pending callbacks
func (k Keeper) GetAllCallbacks(ctx sdk.Context) (callbacks []types.Callback, err error) {
err = k.Callbacks.Walk(ctx, nil, func(key collections.Triple[int64, []byte, uint64], value types.Callback) (bool, error) {
callbacks = append(callbacks, value)
return false, nil
})
return callbacks, err
}

// GetCallbacksByHeight returns the callbacks registered for the given height
func (k Keeper) GetCallbacksByHeight(ctx sdk.Context, height int64) (callbacks []types.Callback, err error) {
rng := collections.NewPrefixedTripleRange[int64, []byte, uint64](height)
err = k.Callbacks.Walk(ctx, rng, func(key collections.Triple[int64, []byte, uint64], value types.Callback) (bool, error) {
callbacks = append(callbacks, value)
return false, nil
})
return callbacks, err
}

// ExistsCallback returns true if the callback exists for height with same contract address and same job id
func (k Keeper) ExistsCallback(ctx sdk.Context, height int64, contractAddress sdk.AccAddress, jobID uint64) (bool, error) {
return k.Callbacks.Has(ctx, collections.Join3(height, contractAddress.Bytes(), jobID))
}

// DeleteCallback deletes a callback given the height, contract address and job id
func (k Keeper) DeleteCallback(ctx sdk.Context, sender string, height int64, contractAddress sdk.AccAddress, jobID uint64) error {
// If callback delete is requested by someone who is not authorized, return error
if !isAuthorizedToModify(ctx, k, height, contractAddress, sender) {
return types.ErrUnauthorized
}
// If a callback with same job id does not exist, return error
exists, err := k.ExistsCallback(ctx, height, contractAddress, jobID)
if err != nil {
return err
}
if !exists {
return types.ErrCallbackNotFound
}
return k.Callbacks.Remove(ctx, collections.Join3(height, contractAddress.Bytes(), jobID))
}

// SaveCallback saves a callback given the height, contract address and job id and callback data
func (k Keeper) SaveCallback(ctx sdk.Context, callback types.Callback) error {
contractAddress := sdk.MustAccAddressFromBech32(callback.GetContractAddress())
// If contract with given address does not exist, return error
if !k.wasmKeeper.HasContractInfo(ctx, contractAddress) {
return types.ErrContractNotFound
}
// If callback is requested by someone which is not authorized, return error
if !isAuthorizedToModify(ctx, k, callback.GetCallbackHeight(), contractAddress, callback.ReservedBy) {
return types.ErrUnauthorized
}
// If a callback with same job id exists at same height, return error
exists, err := k.ExistsCallback(ctx, callback.GetCallbackHeight(), contractAddress, callback.GetJobId())
if err != nil {
return err
}
if !exists {
return types.ErrCallbackNotFound
}
// If callback is requested for height in the past or present, return error
if callback.GetCallbackHeight() <= ctx.BlockHeight() {
return types.ErrCallbackHeightNotinFuture
}

return k.Callbacks.Set(ctx, collections.Join3(callback.GetCallbackHeight(), contractAddress.Bytes(), callback.GetJobId()), callback)
}

func isAuthorizedToModify(ctx sdk.Context, k Keeper, height int64, contractAddress sdk.AccAddress, sender string) bool {
if sender == contractAddress.String() { // A contract can modify its own callbacks
return true
}

contractInfo := k.wasmKeeper.GetContractInfo(ctx, contractAddress)
if sender == contractInfo.Admin { // Admin of the contract can modify its callbacks
return true
}

contractMetadata := k.rewardsKeeper.GetContractMetadata(ctx, contractAddress)
return sender == contractMetadata.OwnerAddress // Owner of the contract can modify its callbacks
}
41 changes: 34 additions & 7 deletions x/callback/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,47 @@ import (

// Keeper provides module state operations.
type Keeper struct {
cdc codec.Codec
storeKey storetypes.StoreKey
cdc codec.Codec
storeKey storetypes.StoreKey
wasmKeeper types.WasmKeeperExpected
rewardsKeeper types.RewardsKeeperExpected

Schema collections.Schema

// Params key: ParamsKeyPrefix | value: Params
Params collections.Item[types.Params]
// Callbacks key: CallbackKeyPrefix | value: []Callback
Callbacks collections.Map[collections.Triple[int64, []byte, uint64], types.Callback]
}

// NewKeeper creates a new Keeper instance.
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey) Keeper {
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, wk types.WasmKeeperExpected, rk types.RewardsKeeperExpected) Keeper {
sb := collections.NewSchemaBuilder(collcompat.NewKVStoreService(storeKey))
return Keeper{
cdc: cdc,
storeKey: storeKey,
Params: collections.NewItem(sb, types.ParamsKeyPrefix, "params", collcompat.ProtoValue[types.Params](cdc)),
k := Keeper{
cdc: cdc,
storeKey: storeKey,
wasmKeeper: wk,
rewardsKeeper: rk,
Params: collections.NewItem(
sb,
types.ParamsKeyPrefix,
"params",
collcompat.ProtoValue[types.Params](cdc),
),
Callbacks: collections.NewMap(
sb,
types.CallbackKeyPrefix,
"callbacks",
collections.TripleKeyCodec(collections.Int64Key, collections.BytesKey, collections.Uint64Key),
collcompat.ProtoValue[types.Callback](cdc),
),
}
schema, err := sb.Build()
if err != nil {
panic(err)
}
k.Schema = schema
return k
}

// Logger returns a module-specific logger.
Expand Down
53 changes: 53 additions & 0 deletions x/callback/types/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package types

import sdk "github.com/cosmos/cosmos-sdk/types"

// NewCallback creates a new Callback instance.
func NewCallback(sender string, contractAddress sdk.AccAddress, height int64, jobID uint64, txFees []*sdk.Coin, blockReservationFees []*sdk.Coin, futureReservationFees []*sdk.Coin, surplusFees []*sdk.Coin) Callback {
return Callback{
ContractAddress: contractAddress.String(),
CallbackHeight: height,
JobId: jobID,
ReservedBy: sender,
FeeSplit: &CallbackFeesFeeSplit{
TransactionFees: txFees,
BlockReservationFees: blockReservationFees,
FutureReservationFees: futureReservationFees,
SurplusFees: surplusFees,
},
}
}

// Validate perform object fields validation.
func (c Callback) Validate() error {
if _, err := sdk.AccAddressFromBech32(c.GetContractAddress()); err != nil {
return err
}
if _, err := sdk.AccAddressFromBech32(c.GetReservedBy()); err != nil {
return err
}
if c.GetCallbackHeight() <= 0 {
return ErrCallbackHeightNotinFuture
}
for _, coin := range c.GetFeeSplit().GetTransactionFees() {
if err := coin.Validate(); err != nil {
return err
}
}
for _, coin := range c.GetFeeSplit().GetBlockReservationFees() {
if err := coin.Validate(); err != nil {
return err
}
}
for _, coin := range c.GetFeeSplit().GetFutureReservationFees() {
if err := coin.Validate(); err != nil {
return err
}
}
for _, coin := range c.GetFeeSplit().GetSurplusFees() {
if err := coin.Validate(); err != nil {
return err
}
}
return nil
}
82 changes: 41 additions & 41 deletions x/callback/types/callback.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions x/callback/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package types
import errorsmod "cosmossdk.io/errors"

var (
DefaultCodespace = ModuleName
ErrInternal = errorsmod.Register(DefaultCodespace, 0, "internal error") // smth went wrong
DefaultCodespace = ModuleName
ErrContractNotFound = errorsmod.Register(DefaultCodespace, 2, "contract with given address not found")
ErrCallbackJobIDExists = errorsmod.Register(DefaultCodespace, 3, "callback with given job id already exists for given height")
ErrCallbackHeightNotinFuture = errorsmod.Register(DefaultCodespace, 4, "callback request height is not in the future")
ErrUnauthorized = errorsmod.Register(DefaultCodespace, 5, "sender not authorized to register callback")
ErrCallbackNotFound = errorsmod.Register(DefaultCodespace, 6, "callback with given job id does not exist for given height")
)
Loading
Loading