The BVSSquaring library

One of the main reasons for the library’s existence is to centralize code that would otherwise be dispersed across various locations. This is true for the BVSSquaring library, an abstraction library designed to streamline interactions with the specific BVS contract used in the Hello World BVS demo. Multiple programs (such as the aggregator, monitor, caller, etc.) can share its functionalities, enhancing reusability and testability.

We recommend each BVS to have it's own BVS abstraction library. However, know that the abstraction library may look quite different from the BVSSquaring example as the needs could be vastly different,

// Interface declerations. The BindClient contains methods to create a new task,
// respond to task, read the input of a task, read the result of task and finishing
// initliaztion of a BVSSquaring instance
type BVSSquaring interface {
    BindClient(string)
    CreateNewTask(context.Context, int64) (*coretypes.ResultTx, error)
    RespondToTask(ctx context.Context, taskId int64, result int64, operators string) (*coretypes.ResultTx, error)
    GetTaskInput(int64) (*wasmtypes.QuerySmartContractStateResponse, error)
    GetTaskResult(int64) (*wasmtypes.QuerySmartContractStateResponse, error)
}

// The implementation of BVSSquaring.
type bvsSquaringImpl struct {
    io             io.ChainIO
    executeOptions *types.ExecuteOptions
    queryOptions   *types.QueryOptions
}

// BindClient finishes initlization first made by NewBVSSquaring()
// it setups contract execution and query options to be used later. 
func (a *bvsSquaringImpl) BindClient(contractAddress string) {
    // Setup contract execution options
    a.executeOptions = &types.ExecuteOptions{
        ContractAddr:  contractAddress,
        ExecuteMsg:    []byte{},
        Funds:         "",
        GasAdjustment: 1.2,
        GasPrice:      sdktypes.NewInt64DecCoin("uosmo", 1),
        Gas:           200000,
        Memo:          "test tx",
        Simulate:      true,
    }

    // Setup query options
    a.queryOptions = &types.QueryOptions{
        ContractAddr: contractAddress,
        QueryMsg:     []byte{},
    }
}

// Under the hood, CreateNewTask() invokes the cotract with the CreateNewTaskReq type
// this triggers the CosmWasm contract's execute() method
func (a *bvsSquaringImpl) CreateNewTask(ctx context.Context, input int64) (*coretypes.ResultTx, error) {
    // Prepare the message to be sent to the contract. Here we instenshate a variable
    // of type CreateNewTaskReq and containing our input variable
    msg := types.CreateNewTaskReq{
        CreateNewTask: types.CreateNewTask{
            Input: input,
        },
    }

    // Serialze the message and assign it into executeOptions
    msgBytes, err := json.Marshal(msg)
    (*a.executeOptions).ExecuteMsg = msgBytes
    if err != nil {
        return nil, err
    }

    // SendTransaction invokes the contract's execut() method on-chain
    return a.io.SendTransaction(ctx, *a.executeOptions)
}

// RespondToTask() is used by aggregator to send results back to the BVS contract
func (a *bvsSquaringImpl) RespondToTask(ctx context.Context, taskId int64, result int64, operators string) (*coretypes.ResultTx, error) {
    // Like CreateNewTask(). It constructs the message to be sent to the contract
    msg := types.RespondToTaskReq{
        RespondToTask: types.RespondToTask{
            TaskId:    taskId,
            Result:    result,
            Operators: operators,
        },
    }

    // Serialze the message and assign it into executeOptions
    msgBytes, err := json.Marshal(msg)
    (*a.executeOptions).ExecuteMsg = msgBytes
    if err != nil {
        return nil, err
    }

    // SendTransaction invokes the contract's execut() method on-chain 
    return a.io.SendTransaction(ctx, *a.executeOptions)
}

// GetTaskInput() queries the BVS contract about the input of a past task
func (a *bvsSquaringImpl) GetTaskInput(taskId int64) (*wasmtypes.QuerySmartContractStateResponse, error) {
    // You'd be very familare with this now. Construt a message of the same type
    // as the ones accepts on-chain.
    msg := types.GetTaskInputReq{
        GetTaskInput: types.GetTaskInput{
            TaskId: taskId,
        },
    }

    // Serialize the message. Unlike above examples we don't need to execute
    // anything (there's nothing to write, we only want to read), thus Query()
    // will be the method we use
    msgBytes, err := json.Marshal(msg)
    (*a.queryOptions).QueryMsg = msgBytes

    if err != nil {
        return nil, err
    }

    // Queries the contract and return result
    return a.io.QueryContract(*a.queryOptions)
}

func (a *bvsSquaringImpl) GetTaskResult(taskId int64) (*wasmtypes.QuerySmartContractStateResponse, error) {
    // DITTO. Construt a message to be sent to the contact
    msg := types.GetTaskResultReq{
        GetTaskResult: types.GetTaskResult{
            TaskId: taskId,
        },
    }

    // Serialize the message and prepare to qeury the contract
    msgBytes, err := json.Marshal(msg)
    (*a.queryOptions).QueryMsg = msgBytes

    if err != nil {
        return nil, err
    }

    // Queries the contract and return result
    return a.io.QueryContract(*a.queryOptions)
}

// Create a new instance of BVSSquaring
func NewBVSSquaring(chainIO io.ChainIO) BVSSquaring {
    return &bvsSquaringImpl{
        io: chainIO,
    }
}

Last updated