diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index 65efcd1333..9e61d7aaa0 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -275,15 +275,29 @@ func (c *runCmd) createCallParams(ctx context.Context, db state.Immutable, param for _, param := range params { switch param.Type { case String, ID: - val, ok := param.Value.(string) + stepIdStr, ok := param.Value.(string) if !ok { return nil, fmt.Errorf("%w: %s", ErrFailedParamTypeCast, param.Type) } - val, err := c.verifyProgramIDStr(val) - if err != nil { - return nil, err + if strings.HasPrefix(stepIdStr, "step_") { + programIdStr, ok := c.programIDStrMap[stepIdStr] + if !ok { + return nil, fmt.Errorf("failed to map to id: %s", stepIdStr) + } + programId, err := ids.FromString(programIdStr) + if err != nil { + return nil, err + } + cp = append(cp, actions.CallParam{Value: programId}) + } else { + programId, err := ids.FromString(stepIdStr) + if err == nil { + cp = append(cp, actions.CallParam{Value: programId}) + } else { + // this is a path to the wasm program + cp = append(cp, actions.CallParam{Value: stepIdStr}) + } } - cp = append(cp, actions.CallParam{Value: val}) case Bool: val, ok := param.Value.(bool) if !ok { @@ -334,28 +348,6 @@ func (c *runCmd) createCallParams(ctx context.Context, db state.Immutable, param return cp, nil } -// verifyProgramIDStr verifies a string is a valid ID and checks the programIDStrMap for -// the synthetic identifier `step_N` where N is the step the id was created from -// execution. -func (c *runCmd) verifyProgramIDStr(idStr string) (string, error) { - // if the id is valid - _, err := ids.FromString(idStr) - if err == nil { - return idStr, nil - } - - // check if the id is a synthetic identifier - if strings.HasPrefix(idStr, "step_") { - stepID, ok := c.programIDStrMap[idStr] - if !ok { - return "", fmt.Errorf("failed to map to id: %s", idStr) - } - return stepID, nil - } - - return idStr, nil -} - // generateRandomID creates a unique ID. // Note: ids.GenerateID() is not used because the IDs are not unique and will // collide. diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 2a1f351935..280f08f0e1 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" "github.com/near/borsh-go" @@ -22,9 +23,9 @@ import ( "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" "github.com/ava-labs/hypersdk/x/programs/examples/imports/pstate" + "github.com/ava-labs/hypersdk/x/programs/examples/storage" "github.com/ava-labs/hypersdk/x/programs/runtime" ) @@ -67,17 +68,16 @@ func (t *ProgramExecute) Execute( return false, 1, OutputValueZero, nil } - programIDStr, ok := t.Params[0].Value.(string) + programID, ok := t.Params[0].Value.(ids.ID) if !ok { return false, 1, utils.ErrBytes(fmt.Errorf("invalid call param: must be ID")), nil } // TODO: take fee out of balance? - programID, err := ids.FromString(programIDStr) - if err != nil { - return false, 1, utils.ErrBytes(err), nil + programBytes, exists, err := storage.GetProgram(context.Background(), mu, programID) + if !exists { + err = errors.New("unknown program") } - programBytes, err := storage.GetProgram(ctx, mu, programID) if err != nil { return false, 1, utils.ErrBytes(err), nil } diff --git a/x/programs/cmd/simulator/vm/storage/storage.go b/x/programs/cmd/simulator/vm/storage/storage.go index c3a6b70728..f8de82fd93 100644 --- a/x/programs/cmd/simulator/vm/storage/storage.go +++ b/x/programs/cmd/simulator/vm/storage/storage.go @@ -15,6 +15,8 @@ import ( "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" + + "github.com/ava-labs/hypersdk/x/programs/examples/storage" ) const ( @@ -58,10 +60,18 @@ func GetProgram( programID ids.ID, ) ( []byte, // program bytes + bool, // exists error, ) { k := ProgramKey(programID) - return db.GetValue(ctx, k) + v, err := db.GetValue(ctx, k) + if errors.Is(err, database.ErrNotFound) { + return nil, false, nil + } + if err != nil { + return nil, false, err + } + return v, true, nil } // setProgram stores [program] at [programID] @@ -71,8 +81,7 @@ func SetProgram( programID ids.ID, program []byte, ) error { - k := ProgramKey(programID) - return mu.Insert(ctx, k, program) + return storage.SetProgram(ctx, mu, programID, program) } // diff --git a/x/programs/examples/counter_test.go b/x/programs/examples/counter_test.go index 42601db8a9..c6ea953d84 100644 --- a/x/programs/examples/counter_test.go +++ b/x/programs/examples/counter_test.go @@ -28,7 +28,7 @@ import ( func TestCounterProgram(t *testing.T) { require := require.New(t) db := newTestDB() - maxUnits := uint64(80000) + maxUnits := uint64(10000000) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -102,7 +102,7 @@ func TestCounterProgram(t *testing.T) { require.NoError(err) // define max units to transfer to second runtime - unitsTransfer := uint64(20000) + unitsTransfer := uint64(2000000) // transfer the units from the original runtime to the new runtime before // any calls are made. @@ -180,7 +180,7 @@ func TestCounterProgram(t *testing.T) { target, err := writeToMem(programID2, mem) require.NoError(err) - maxUnitsProgramToProgram := int64(10000) + maxUnitsProgramToProgram := int64(1000000) maxUnitsProgramToProgramPtr, err := writeToMem(maxUnitsProgramToProgram, mem) require.NoError(err) diff --git a/x/programs/examples/imports/program/program.go b/x/programs/examples/imports/program/program.go index 37fca43eeb..80f0eff35e 100644 --- a/x/programs/examples/imports/program/program.go +++ b/x/programs/examples/imports/program/program.go @@ -6,6 +6,7 @@ package program import ( "context" "encoding/binary" + "errors" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" @@ -217,6 +218,7 @@ func getProgramWasmBytes(log logging.Logger, db state.Immutable, idBytes []byte) bytes, exists, err := storage.GetProgram(context.Background(), db, id) if !exists { log.Debug("key does not exist", zap.String("id", id.String())) + return nil, errors.New("unknown program") } if err != nil { return nil, err diff --git a/x/programs/examples/storage/storage.go b/x/programs/examples/storage/storage.go index 7fef5ae0e4..c42759879a 100644 --- a/x/programs/examples/storage/storage.go +++ b/x/programs/examples/storage/storage.go @@ -15,7 +15,8 @@ import ( ) const ( - programPrefix = 0x0 + programPrefix = 0x0 + ProgramChunks uint16 = 1 ) func ProgramPrefixKey(id []byte, key []byte) (k []byte) { diff --git a/x/programs/rust/examples/counter/Cargo.toml b/x/programs/rust/examples/counter/Cargo.toml index 82de191709..07861114e8 100644 --- a/x/programs/rust/examples/counter/Cargo.toml +++ b/x/programs/rust/examples/counter/Cargo.toml @@ -8,6 +8,9 @@ edition = "2021" [dependencies] wasmlanche-sdk = { version = "0.1.0", path = "../../wasmlanche-sdk" } +[dev-dependencies] +simulator = { path = "../../../cmd/simulator/" } + [build-dependencies] wasmlanche-sdk = { path = "../../wasmlanche-sdk", features = ["build"] } diff --git a/x/programs/rust/examples/counter/src/lib.rs b/x/programs/rust/examples/counter/src/lib.rs index fa366541cd..b572af8777 100644 --- a/x/programs/rust/examples/counter/src/lib.rs +++ b/x/programs/rust/examples/counter/src/lib.rs @@ -66,3 +66,241 @@ pub fn get_value_external(_: Context, target: Program, max_units: i64, of: Addre .call_function("get_value", params, max_units) .unwrap() } + +#[cfg(test)] +mod tests { + use simulator::{Endpoint, Key, Param, Plan, Require, ResultAssertion, Step}; + + const PROGRAM_PATH: &str = env!("PROGRAM_PATH"); + + #[test] + fn init_program() { + let simulator = simulator::Client::new(); + + let owner_key = String::from("owner"); + let alice_key = Param::Key(Key::Ed25519(String::from("alice"))); + + let mut plan = Plan::new(owner_key.clone()); + + plan.add_step(Step::create_key(Key::Ed25519(owner_key))); + + plan.add_step(Step { + endpoint: Endpoint::Key, + method: "key_create".into(), + params: vec![alice_key.clone()], + max_units: 0, + require: None, + }); + + let counter1_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 1000000, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "initialize_address".into(), + max_units: 1000000, + params: vec![counter1_id.into(), alice_key.clone()], + require: None, + }); + + // run plan + let plan_responses = simulator.run_plan(&plan).unwrap(); + + // ensure no errors + assert!( + plan_responses.iter().all(|resp| resp.error.is_none()), + "error: {:?}", + plan_responses + .iter() + .filter_map(|resp| resp.error.as_ref()) + .next() + ); + } + + #[test] + fn increment() { + let simulator = simulator::Client::new(); + + let owner_key = String::from("owner"); + let bob_key = Param::Key(Key::Ed25519(String::from("bob"))); + + let mut plan = Plan::new(owner_key.clone()); + + plan.add_step(Step::create_key(Key::Ed25519(owner_key))); + + plan.add_step(Step { + endpoint: Endpoint::Key, + method: "key_create".into(), + params: vec![bob_key.clone()], + max_units: 0, + require: None, + }); + + let counter1_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 1000000, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "initialize_address".into(), + max_units: 1000000, + params: vec![counter1_id.into(), bob_key.clone()], + require: None, + }); + + let counter2_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 1000000, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "initialize_address".into(), + max_units: 1000000, + params: vec![counter2_id.into(), bob_key.clone()], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "inc".into(), + max_units: 1000000, + params: vec![counter2_id.into(), bob_key.clone(), 10.into()], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::ReadOnly, + method: "get_value".into(), + max_units: 0, + params: vec![counter2_id.into(), bob_key.clone()], + require: Some(Require { + result: ResultAssertion::NumericEq(10), + }), + }); + + // run plan + let plan_responses = simulator.run_plan(&plan).unwrap(); + + // ensure no errors + assert!( + plan_responses.iter().all(|resp| resp.error.is_none()), + "error: {:?}", + plan_responses + .iter() + .filter_map(|resp| resp.error.as_ref()) + .next() + ); + } + + #[test] + fn external_call() { + let simulator = simulator::Client::new(); + + let owner_key = String::from("owner"); + let bob_key = Param::Key(Key::Ed25519(String::from("bob"))); + + let mut plan = Plan::new(owner_key.clone()); + + plan.add_step(Step::create_key(Key::Ed25519(owner_key))); + + plan.add_step(Step { + endpoint: Endpoint::Key, + method: "key_create".into(), + params: vec![bob_key.clone()], + max_units: 0, + require: None, + }); + + let counter1_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 1000000, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "initialize_address".into(), + max_units: 1000000, + params: vec![counter1_id.into(), bob_key.clone()], + require: None, + }); + + let counter2_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 1000000, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "initialize_address".into(), + max_units: 1000000, + params: vec![counter2_id.into(), bob_key.clone()], + require: None, + }); + plan.add_step(Step { + endpoint: Endpoint::ReadOnly, + method: "get_value".into(), + max_units: 0, + params: vec![counter2_id.into(), bob_key.clone()], + require: Some(Require { + result: ResultAssertion::NumericEq(0), + }), + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "inc_external".into(), + max_units: 100000000, + params: vec![ + counter1_id.into(), + counter2_id.into(), + 1000000.into(), + bob_key.clone(), + 10.into(), + ], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::ReadOnly, + method: "get_value_external".into(), + max_units: 0, + params: vec![ + counter1_id.into(), + counter2_id.into(), + 1000000.into(), + bob_key.clone(), + ], + require: Some(Require { + result: ResultAssertion::NumericEq(10), + }), + }); + + // run plan + let plan_responses = simulator.run_plan(&plan).unwrap(); + + // ensure no errors + assert!( + plan_responses.iter().all(|resp| resp.error.is_none()), + "error: {:?}", + plan_responses + .iter() + .filter_map(|resp| resp.error.as_ref()) + .next() + ); + } +} diff --git a/x/programs/tests/fixture/counter.wasm b/x/programs/tests/fixture/counter.wasm index 232da1db69..0f811555bf 100755 Binary files a/x/programs/tests/fixture/counter.wasm and b/x/programs/tests/fixture/counter.wasm differ