Skip to content

Add contract write runner #111

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

Merged
merged 14 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ jobs:
environment: Test
name: Unit Test
runs-on: ubuntu-latest
strategy:
matrix:
tests:
- ./core/taskengine
- ./core/taskengine/trigger
- ./core/taskengine/macros
- ./pkg/timekeeper
- ./pkg/graphql
- ./aggregator/

steps:
- uses: actions/checkout@v4
with:
Expand All @@ -19,11 +29,7 @@ jobs:
RPC_URL: "${{ secrets.RPC_URL }}"
BUNDLER_RPC: "${{ secrets.BUNDLER_RPC }}"
run: |
# TODO Implement test for all packages
go test -v ./core/taskengine
go test -v ./core/taskengine/trigger
go test -v ./core/taskengine/macros
go test -v ./pkg/timekeeper
go test -v ${{ matrix.test }}

publish-dev-build:
name: Publish dev build docker image to dockerhub
Expand Down
288 changes: 91 additions & 197 deletions core/taskengine/vm_runner_contract_write.go
Original file line number Diff line number Diff line change
@@ -1,199 +1,93 @@
package taskengine

//
// import (
// "context"
// "encoding/json"
// "fmt"
// "strings"
// "time"
//
// "github.com/ethereum/go-ethereum"
// "github.com/ethereum/go-ethereum/accounts/abi"
// "github.com/ethereum/go-ethereum/common"
// "github.com/ethereum/go-ethereum/ethclient"
//
// avsproto "github.com/AvaProtocol/ap-avs/protobuf"
// )
//
// type ContractWriteProcessor struct {
// *CommonProcessor
// client *ethclient.Client
// }
//
// func NewContractWriteProcessor(vm *VM, client *ethclient.Client) *ContractWriteProcessor {
// return &ContractWriteProcessor{
// client: client,
// CommonProcessor: &CommonProcessor{
// vm: vm,
// },
// }
// }
//
// func (r *ContractWriteProcessor) Execute(stepID string, node *avsproto.ContractWriteNode) (*avsproto.Execution_Step, error) {
// ctx := context.Background()
// t0 := time.Now().Unix()
// s := &avsproto.Execution_Step{
// NodeId: stepID,
// Log: "",
// OutputData: "",
// Success: true,
// Error: "",
// StartAt: t0,
// }
//
// var err error
// defer func() {
// s.EndAt = time.Now().Unix()
// s.Success = err == nil
// if err != nil {
// s.Error = err.Error()
// }
// }()
//
// var log strings.Builder
//
// // TODO: support load pre-define ABI
// parsedABI, err := abi.JSON(strings.NewReader(node.ContractAbi))
// if err != nil {
// return nil, fmt.Errorf("error parse abi: %w", err)
// }
//
// contractAddress := common.HexToAddress(node.ContractAddress)
// calldata := common.FromHex(node.CallData)
// msg := ethereum.CallMsg{
// To: &contractAddress,
// Data: calldata,
// }
//
// output, err := r.client.CallContract(ctx, msg, nil)
//
// if err != nil {
// s.Success = false
// s.Error = fmt.Errorf("error invoke contract method: %w", err).Error()
// return s, err
// }
//
// // Unpack the output
// result, err := parsedABI.Unpack(node.Method, output)
// if err != nil {
// s.Success = false
// s.Error = fmt.Errorf("error decode result: %w", err).Error()
// return s, err
// }
//
// log.WriteString(fmt.Sprintf("Call %s on %s at %s", node.Method, node.ContractAddress, time.Now()))
// s.Log = log.String()
// outputData, err := json.Marshal(result)
// s.OutputData = string(outputData)
// r.SetOutputVarForStep(stepID, outputData)
// if err != nil {
// s.Success = false
// s.Error = err.Error()
// return s, err
// }
//
// return s, nil
// }
//
// //type ContractProcessor struct {
// // db storage.Storage
// // smartWalletConfig *config.SmartWalletConfig
// // logger sdklogging.Logger
// //}
// //
// //func (c *ContractProcessor) GetTask(id string) (*model.Task, error) {
// // var task model.Task
// // item, err := c.db.GetKey([]byte(fmt.Sprintf("t:%s:%s", TaskStatusToStorageKey(avsproto.TaskStatus_Executing), id)))
// //
// // if err != nil {
// // return nil, err
// // }
// // err = json.Unmarshal(item, &task)
// // if err != nil {
// // return nil, err
// // }
// //
// // return &task, nil
// // }
// // func (c *ContractProcessor) ContractWrite(job *apqueue.Job) error {
// // //currentTime := time.Now()
// //
// // conn, _ := ethclient.Dial(c.smartWalletConfig.EthRpcUrl)
// // defer conn.Close()
// //
// // // Because we used the master key to signed, the address cannot be
// // // calculate from that key, but need to be passed in instead
// // task, err := c.GetTask(string(job.Data))
// // if err != nil {
// // return err
// // }
// //
// // defer func() {
// // updates := map[string][]byte{}
// // updates[string(TaskStorageKey(task.Id, avsproto.TaskStatus_Executing))], err = task.ToJSON()
// // updates[string(TaskUserKey(task))] = []byte(fmt.Sprintf("%d", task.Status))
// //
// // if err = c.db.BatchWrite(updates); err == nil {
// // c.db.Move(
// // []byte(TaskStorageKey(task.Id, avsproto.TaskStatus_Executing)),
// // []byte(TaskStorageKey(task.Id, task.Status)),
// // )
// // } else {
// // // TODO Gracefully handling of storage cleanup
// // }
// // }()
// //
// // // TODO: Implement the actualy nodes exeuction engine
// // // Process entrypoint node, then from the next pointer, and flow of the node, we will follow the chain of execution
// // action := task.Nodes[0]
// //
// // // TODO: move to vm.go
// // if action.GetContractWrite() == nil {
// // err := fmt.Errorf("invalid task action")
// // //task.AppendExecution(currentTime.Unix(), "", err)
// // task.SetFailed()
// // return err
// // }
// //
// // userOpCalldata, e := aa.PackExecute(
// // common.HexToAddress(action.GetContractWrite().ContractAddress),
// // big.NewInt(0),
// // common.FromHex(action.GetContractWrite().CallData),
// // )
// // //calldata := common.FromHex("b61d27f600000000000000000000000069256ca54e6296e460dec7b29b7dcd97b81a3d55000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000e0f7d11fd714674722d325cd86062a5f1882e13a0000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000")
// //
// // owner := common.HexToAddress(task.Owner)
// // bundlerClient, e := bundler.NewBundlerClient(c.smartWalletConfig.BundlerURL)
// // if e != nil {
// // // TODO: maybe set retry?
// // err := fmt.Errorf("internal error, bundler not available")
// // //task.AppendExecution(currentTime.Unix(), "", err)
// // task.SetFailed()
// // return err
// // }
// //
// // c.logger.Info("send task to bundler rpc", "task_id", task.Id)
// // txResult, err := preset.SendUserOp(
// // conn,
// // bundlerClient,
// // c.smartWalletConfig.ControllerPrivateKey,
// // owner,
// // userOpCalldata,
// // )
// //
// // if txResult != "" {
// // // only set complete when the task is not reaching max
// // // task.SetCompleted()
// // c.logger.Info("succesfully perform userop", "task_id", task.Id, "userop", txResult)
// // } else {
// // task.SetFailed()
// // c.logger.Error("err perform userop", "task_id", task.Id, "error", err)
// // }
// //
// // if err != nil || txResult == "" {
// // return fmt.Errorf("UseOp failed to send; error: %v", err)
// // }
// //
// // return nil
// // }
import (
"fmt"
"math/big"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"

"github.com/AvaProtocol/ap-avs/core/chainio/aa"
"github.com/AvaProtocol/ap-avs/core/config"
"github.com/AvaProtocol/ap-avs/pkg/erc4337/bundler"
"github.com/AvaProtocol/ap-avs/pkg/erc4337/preset"
avsproto "github.com/AvaProtocol/ap-avs/protobuf"
)

type ContractWriteProcessor struct {
*CommonProcessor
client *ethclient.Client
smartWalletConfig *config.SmartWalletConfig
owner common.Address
}

func NewContractWriteProcessor(vm *VM, client *ethclient.Client, smartWalletConfig *config.SmartWalletConfig, owner common.Address) *ContractWriteProcessor {
return &ContractWriteProcessor{
client: client,
smartWalletConfig: smartWalletConfig,
owner: owner,
CommonProcessor: &CommonProcessor{
vm: vm,
},
}
}

func (r *ContractWriteProcessor) Execute(stepID string, node *avsproto.ContractWriteNode) (*avsproto.Execution_Step, error) {
t0 := time.Now().Unix()
s := &avsproto.Execution_Step{
NodeId: stepID,
Log: "",
OutputData: "",
Success: true,
Error: "",
StartAt: t0,
}

var log strings.Builder
var err error

defer func() {
s.Log = log.String()
s.EndAt = time.Now().Unix()
s.Success = err == nil
}()

contractAddress := common.HexToAddress(node.ContractAddress)
calldata := common.FromHex(node.CallData)
userOpCalldata, err := aa.PackExecute(
contractAddress,
big.NewInt(0), // TODO: load correct salt from the task
calldata,
)
log.WriteString("\ncreate bundle client to send userops to bundler rpc\n")
bundlerClient, err := bundler.NewBundlerClient(r.smartWalletConfig.BundlerURL)

if err != nil {
log.WriteString(fmt.Sprintf("error creating bundle client: %s", err))
s.Error = fmt.Sprintf("error creating bundler client : %s", err)
return s, err
}

log.WriteString("\nsend userops to bundler rpc\n")
fmt.Println("send userops to bundler rpc")

txResult, err := preset.SendUserOp(
r.client,
bundlerClient,
r.smartWalletConfig.ControllerPrivateKey,
r.owner,
userOpCalldata,
)

if err != nil {
s.Error = fmt.Sprintf("error send userops to bundler : %s", err)
return s, err
}

s.OutputData = txResult
r.SetOutputVarForStep(stepID, txResult)

return s, nil
}
25 changes: 16 additions & 9 deletions pkg/erc4337/bundler/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,14 @@ func (bc *BundlerClient) EstimateUserOperationGas(
override map[string]any,
) (*GasEstimation, error) {
var result struct {
PreVerificationGas int64 `json:"preVerificationGas"`
VerificationGasLimit int64 `json:"verificationGasLimit"`
CallGasLimit int64 `json:"callGasLimit"`
VerificationGas int64 `json:"verificationGas"`
// PreVerificationGas int64 `json:"preVerificationGas"`
// VerificationGasLimit int64 `json:"verificationGasLimit"`
// CallGasLimit int64 `json:"callGasLimit"`
// VerificationGas int64 `json:"verificationGas"`

PreVerificationGas string `json:"preVerificationGas"`
VerificationGasLimit string `json:"verificationGasLimit"`
CallGasLimit string `json:"callGasLimit"`
}

uo := UserOperation{
Expand All @@ -99,12 +103,15 @@ func (bc *BundlerClient) EstimateUserOperationGas(
PreVerificationGas: new(big.Int),
VerificationGasLimit: new(big.Int),
CallGasLimit: new(big.Int),
VerificationGas: new(big.Int),
//VerificationGas: new(big.Int),
}
gasEstimation.PreVerificationGas.SetInt64(result.PreVerificationGas)
gasEstimation.VerificationGasLimit.SetInt64(result.VerificationGasLimit)
gasEstimation.CallGasLimit.SetInt64(result.CallGasLimit)
gasEstimation.VerificationGas.SetInt64(result.VerificationGas)
// gasEstimation.PreVerificationGas.SetInt64(result.PreVerificationGas)
// gasEstimation.VerificationGasLimit.SetInt64(result.VerificationGasLimit)
// gasEstimation.CallGasLimit.SetInt64(result.CallGasLimit)
// gasEstimation.VerificationGas.SetInt64(result.VerificationGas)
gasEstimation.PreVerificationGas.SetString(result.PreVerificationGas, 16)
gasEstimation.VerificationGasLimit.SetString(result.VerificationGasLimit, 16)
gasEstimation.CallGasLimit.SetString(result.CallGasLimit, 16)

return gasEstimation, nil
}
Expand Down
19 changes: 12 additions & 7 deletions pkg/erc4337/preset/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (
var (
// Dummy value to fullfil validation.
// Gas info is calculated and return by bundler RPC
callGasLimit = big.NewInt(35000)
verificationGasLimit = big.NewInt(70000)
preVerificationGas = big.NewInt(21000)
callGasLimit = big.NewInt(10000000)
verificationGasLimit = big.NewInt(10000000)
preVerificationGas = big.NewInt(10000000)

// the signature isnt important, only length check
dummySigForGasEstimation = crypto.Keccak256Hash(common.FromHex("0xdead123"))
Expand Down Expand Up @@ -79,10 +79,15 @@ func SendUserOp(
return "", fmt.Errorf("error estimated gas from bundler: %w", e)
}

userOp.PreVerificationGas = gas.PreVerificationGas
userOp.VerificationGasLimit = gas.VerificationGasLimit
userOp.CallGasLimit = gas.CallGasLimit
//userOp.VerificationGas = gas.VerificationGas
// userOp.PreVerificationGas = gas.PreVerificationGas
// userOp.VerificationGasLimit = gas.VerificationGasLimit
// userOp.CallGasLimit = gas.CallGasLimit
// //userOp.VerificationGas = gas.VerificationGas

// TODO: Fix this to load properly estimate from voltaire https://github.com/candidelabs/voltaire
userOp.PreVerificationGas = big.NewInt(10000000) //gas.PreVerificationGas
userOp.VerificationGasLimit = big.NewInt(10000000) //gas.VerificationGasLimit
userOp.CallGasLimit = big.NewInt(10000000) //gas.CallGasLimit

userOpHash := userOp.GetUserOpHash(aa.EntrypointAddress, chainID)
userOp.Signature, _ = signer.SignMessage(signerKey, userOpHash.Bytes())
Expand Down
Loading