github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/native_test.go (about) 1 package compiler_test 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "math/big" 8 "strconv" 9 "strings" 10 "testing" 11 12 "github.com/nspcc-dev/neo-go/pkg/compiler" 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/config/limits" 15 "github.com/nspcc-dev/neo-go/pkg/core/interop" 16 "github.com/nspcc-dev/neo-go/pkg/core/native" 17 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 18 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 19 "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" 20 "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" 21 "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" 22 "github.com/nspcc-dev/neo-go/pkg/interop/native/management" 23 "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" 24 "github.com/nspcc-dev/neo-go/pkg/interop/native/notary" 25 "github.com/nspcc-dev/neo-go/pkg/interop/native/oracle" 26 "github.com/nspcc-dev/neo-go/pkg/interop/native/policy" 27 "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" 28 "github.com/nspcc-dev/neo-go/pkg/interop/native/std" 29 "github.com/nspcc-dev/neo-go/pkg/interop/storage" 30 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 31 "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" 32 "github.com/nspcc-dev/neo-go/pkg/vm" 33 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 34 "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" 35 "github.com/stretchr/testify/require" 36 ) 37 38 func TestContractHashes(t *testing.T) { 39 cfg := config.ProtocolConfiguration{P2PSigExtensions: true} 40 cs := native.NewContracts(cfg) 41 require.Equalf(t, []byte(neo.Hash), cs.NEO.Hash.BytesBE(), "%q", string(cs.NEO.Hash.BytesBE())) 42 require.Equalf(t, []byte(gas.Hash), cs.GAS.Hash.BytesBE(), "%q", string(cs.GAS.Hash.BytesBE())) 43 require.Equalf(t, []byte(oracle.Hash), cs.Oracle.Hash.BytesBE(), "%q", string(cs.Oracle.Hash.BytesBE())) 44 require.Equalf(t, []byte(roles.Hash), cs.Designate.Hash.BytesBE(), "%q", string(cs.Designate.Hash.BytesBE())) 45 require.Equalf(t, []byte(policy.Hash), cs.Policy.Hash.BytesBE(), "%q", string(cs.Policy.Hash.BytesBE())) 46 require.Equalf(t, []byte(ledger.Hash), cs.Ledger.Hash.BytesBE(), "%q", string(cs.Ledger.Hash.BytesBE())) 47 require.Equalf(t, []byte(management.Hash), cs.Management.Hash.BytesBE(), "%q", string(cs.Management.Hash.BytesBE())) 48 require.Equalf(t, []byte(notary.Hash), cs.Notary.Hash.BytesBE(), "%q", string(cs.Notary.Hash.BytesBE())) 49 require.Equalf(t, []byte(crypto.Hash), cs.Crypto.Hash.BytesBE(), "%q", string(cs.Crypto.Hash.BytesBE())) 50 require.Equalf(t, []byte(std.Hash), cs.Std.Hash.BytesBE(), "%q", string(cs.Std.Hash.BytesBE())) 51 } 52 53 func TestContractParameterTypes(t *testing.T) { 54 require.EqualValues(t, management.AnyType, smartcontract.AnyType) 55 require.EqualValues(t, management.BoolType, smartcontract.BoolType) 56 require.EqualValues(t, management.IntegerType, smartcontract.IntegerType) 57 require.EqualValues(t, management.ByteArrayType, smartcontract.ByteArrayType) 58 require.EqualValues(t, management.StringType, smartcontract.StringType) 59 require.EqualValues(t, management.Hash160Type, smartcontract.Hash160Type) 60 require.EqualValues(t, management.Hash256Type, smartcontract.Hash256Type) 61 require.EqualValues(t, management.PublicKeyType, smartcontract.PublicKeyType) 62 require.EqualValues(t, management.SignatureType, smartcontract.SignatureType) 63 require.EqualValues(t, management.ArrayType, smartcontract.ArrayType) 64 require.EqualValues(t, management.MapType, smartcontract.MapType) 65 require.EqualValues(t, management.InteropInterfaceType, smartcontract.InteropInterfaceType) 66 require.EqualValues(t, management.VoidType, smartcontract.VoidType) 67 } 68 69 func TestRoleManagementRole(t *testing.T) { 70 require.EqualValues(t, noderoles.Oracle, roles.Oracle) 71 require.EqualValues(t, noderoles.StateValidator, roles.StateValidator) 72 require.EqualValues(t, noderoles.NeoFSAlphabet, roles.NeoFSAlphabet) 73 require.EqualValues(t, noderoles.P2PNotary, roles.P2PNotary) 74 } 75 76 func TestCryptoLibNamedCurve(t *testing.T) { 77 require.EqualValues(t, native.Secp256k1Sha256, crypto.Secp256k1Sha256) 78 require.EqualValues(t, native.Secp256r1Sha256, crypto.Secp256r1Sha256) 79 require.EqualValues(t, native.Secp256k1Keccak256, crypto.Secp256k1Keccak256) 80 require.EqualValues(t, native.Secp256r1Keccak256, crypto.Secp256r1Keccak256) 81 } 82 83 func TestOracleContractValues(t *testing.T) { 84 require.EqualValues(t, oracle.Success, transaction.Success) 85 require.EqualValues(t, oracle.ProtocolNotSupported, transaction.ProtocolNotSupported) 86 require.EqualValues(t, oracle.ConsensusUnreachable, transaction.ConsensusUnreachable) 87 require.EqualValues(t, oracle.NotFound, transaction.NotFound) 88 require.EqualValues(t, oracle.Timeout, transaction.Timeout) 89 require.EqualValues(t, oracle.Forbidden, transaction.Forbidden) 90 require.EqualValues(t, oracle.ResponseTooLarge, transaction.ResponseTooLarge) 91 require.EqualValues(t, oracle.InsufficientFunds, transaction.InsufficientFunds) 92 require.EqualValues(t, oracle.Error, transaction.Error) 93 94 require.EqualValues(t, oracle.MinimumResponseGas, native.MinimumResponseGas) 95 } 96 97 func TestLedgerTransactionWitnessScope(t *testing.T) { 98 require.EqualValues(t, ledger.None, transaction.None) 99 require.EqualValues(t, ledger.CalledByEntry, transaction.CalledByEntry) 100 require.EqualValues(t, ledger.CustomContracts, transaction.CustomContracts) 101 require.EqualValues(t, ledger.CustomGroups, transaction.CustomGroups) 102 require.EqualValues(t, ledger.Rules, transaction.Rules) 103 require.EqualValues(t, ledger.Global, transaction.Global) 104 } 105 106 func TestLedgerTransactionWitnessAction(t *testing.T) { 107 require.EqualValues(t, ledger.WitnessAllow, transaction.WitnessAllow) 108 require.EqualValues(t, ledger.WitnessDeny, transaction.WitnessDeny) 109 } 110 111 func TestLedgerTransactionWitnessCondition(t *testing.T) { 112 require.EqualValues(t, ledger.WitnessBoolean, transaction.WitnessBoolean) 113 require.EqualValues(t, ledger.WitnessNot, transaction.WitnessNot) 114 require.EqualValues(t, ledger.WitnessAnd, transaction.WitnessAnd) 115 require.EqualValues(t, ledger.WitnessOr, transaction.WitnessOr) 116 require.EqualValues(t, ledger.WitnessScriptHash, transaction.WitnessScriptHash) 117 require.EqualValues(t, ledger.WitnessGroup, transaction.WitnessGroup) 118 require.EqualValues(t, ledger.WitnessCalledByEntry, transaction.WitnessCalledByEntry) 119 require.EqualValues(t, ledger.WitnessCalledByContract, transaction.WitnessCalledByContract) 120 require.EqualValues(t, ledger.WitnessCalledByGroup, transaction.WitnessCalledByGroup) 121 } 122 123 func TestLedgerVMStates(t *testing.T) { 124 require.EqualValues(t, ledger.NoneState, vmstate.None) 125 require.EqualValues(t, ledger.HaltState, vmstate.Halt) 126 require.EqualValues(t, ledger.FaultState, vmstate.Fault) 127 require.EqualValues(t, ledger.BreakState, vmstate.Break) 128 } 129 130 func TestPolicyAttributeType(t *testing.T) { 131 require.EqualValues(t, policy.HighPriorityT, transaction.HighPriority) 132 require.EqualValues(t, policy.OracleResponseT, transaction.OracleResponseT) 133 require.EqualValues(t, policy.NotValidBeforeT, transaction.NotValidBeforeT) 134 require.EqualValues(t, policy.ConflictsT, transaction.ConflictsT) 135 require.EqualValues(t, policy.NotaryAssistedT, transaction.NotaryAssistedT) 136 } 137 138 func TestStorageLimits(t *testing.T) { 139 require.EqualValues(t, storage.MaxKeyLen, limits.MaxStorageKeyLen) 140 require.EqualValues(t, storage.MaxValueLen, limits.MaxStorageValueLen) 141 } 142 143 type nativeTestCase struct { 144 method string 145 params []string 146 } 147 148 // Here we test that corresponding method does exist, is invoked and correct value is returned. 149 func TestNativeHelpersCompile(t *testing.T) { 150 cfg := config.ProtocolConfiguration{P2PSigExtensions: true} 151 cs := native.NewContracts(cfg) 152 u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")` 153 u256 := `interop.Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` 154 pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` 155 sig := `interop.Signature("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` 156 nep17TestCases := []nativeTestCase{ 157 {"balanceOf", []string{u160}}, 158 {"decimals", nil}, 159 {"symbol", nil}, 160 {"totalSupply", nil}, 161 {"transfer", []string{u160, u160, "123", "nil"}}, 162 } 163 runNativeTestCases(t, cs.NEO.ContractMD, "neo", append([]nativeTestCase{ 164 {"getCandidates", nil}, 165 {"getAllCandidates", nil}, 166 {"getCandidateVote", []string{pub}}, 167 {"getCommittee", nil}, 168 {"getCommitteeAddress", nil}, 169 {"getGasPerBlock", nil}, 170 {"getNextBlockValidators", nil}, 171 {"getRegisterPrice", nil}, 172 {"registerCandidate", []string{pub}}, 173 {"setGasPerBlock", []string{"1"}}, 174 {"setRegisterPrice", []string{"10"}}, 175 {"vote", []string{u160, pub}}, 176 {"unclaimedGas", []string{u160, "123"}}, 177 {"unregisterCandidate", []string{pub}}, 178 {"getAccountState", []string{u160}}, 179 }, nep17TestCases...)) 180 runNativeTestCases(t, cs.GAS.ContractMD, "gas", nep17TestCases) 181 runNativeTestCases(t, cs.Oracle.ContractMD, "oracle", []nativeTestCase{ 182 {"getPrice", nil}, 183 {"request", []string{`"url"`, "nil", `"callback"`, "nil", "123"}}, 184 {"setPrice", []string{"10"}}, 185 }) 186 runNativeTestCases(t, cs.Designate.ContractMD, "roles", []nativeTestCase{ 187 {"designateAsRole", []string{"1", "[]interop.PublicKey{}"}}, 188 {"getDesignatedByRole", []string{"1", "1000"}}, 189 }) 190 runNativeTestCases(t, cs.Policy.ContractMD, "policy", []nativeTestCase{ 191 {"blockAccount", []string{u160}}, 192 {"getExecFeeFactor", nil}, 193 {"getFeePerByte", nil}, 194 {"getStoragePrice", nil}, 195 {"isBlocked", []string{u160}}, 196 {"setExecFeeFactor", []string{"42"}}, 197 {"setFeePerByte", []string{"42"}}, 198 {"setStoragePrice", []string{"42"}}, 199 {"unblockAccount", []string{u160}}, 200 {"getAttributeFee", []string{"1"}}, 201 {"setAttributeFee", []string{"1", "123"}}, 202 }) 203 runNativeTestCases(t, cs.Ledger.ContractMD, "ledger", []nativeTestCase{ 204 {"currentHash", nil}, 205 {"currentIndex", nil}, 206 {"getBlock", []string{"1"}}, 207 {"getTransaction", []string{u256}}, 208 {"getTransactionFromBlock", []string{u256, "1"}}, 209 {"getTransactionHeight", []string{u256}}, 210 {"getTransactionSigners", []string{u256}}, 211 {"getTransactionVMState", []string{u256}}, 212 }) 213 runNativeTestCases(t, cs.Notary.ContractMD, "notary", []nativeTestCase{ 214 {"lockDepositUntil", []string{u160, "123"}}, 215 {"withdraw", []string{u160, u160}}, 216 {"balanceOf", []string{u160}}, 217 {"expirationOf", []string{u160}}, 218 {"getMaxNotValidBeforeDelta", nil}, 219 {"setMaxNotValidBeforeDelta", []string{"42"}}, 220 }) 221 runNativeTestCases(t, cs.Management.ContractMD, "management", []nativeTestCase{ 222 {"deploy", []string{"nil", "nil"}}, 223 {"deployWithData", []string{"nil", "nil", "123"}}, 224 {"destroy", nil}, 225 {"getContract", []string{u160}}, 226 {"getContractById", []string{"1"}}, 227 {"getContractHashes", nil}, 228 {"getMinimumDeploymentFee", nil}, 229 {"hasMethod", []string{u160, `"method"`, "0"}}, 230 {"setMinimumDeploymentFee", []string{"42"}}, 231 {"update", []string{"nil", "nil"}}, 232 {"updateWithData", []string{"nil", "nil", "123"}}, 233 }) 234 runNativeTestCases(t, cs.Crypto.ContractMD, "crypto", []nativeTestCase{ 235 {"sha256", []string{"[]byte{1, 2, 3}"}}, 236 {"ripemd160", []string{"[]byte{1, 2, 3}"}}, 237 {"murmur32", []string{"[]byte{1, 2, 3}", "123"}}, 238 {"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1Sha256"}}, 239 {"bls12381Serialize", []string{"crypto.Bls12381Point{}"}}, 240 {"bls12381Deserialize", []string{"[]byte{1, 2, 3}"}}, 241 {"bls12381Equal", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}}, 242 {"bls12381Add", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}}, 243 {"bls12381Mul", []string{"crypto.Bls12381Point{}", "[]byte{1, 2, 3}", "true"}}, 244 {"bls12381Pairing", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}}, 245 {"keccak256", []string{"[]byte{1, 2, 3}"}}, 246 }) 247 runNativeTestCases(t, cs.Std.ContractMD, "std", []nativeTestCase{ 248 {"serialize", []string{"[]byte{1, 2, 3}"}}, 249 {"deserialize", []string{"[]byte{1, 2, 3}"}}, 250 {"jsonSerialize", []string{"[]byte{1, 2, 3}"}}, 251 {"jsonDeserialize", []string{"[]byte{1, 2, 3}"}}, 252 {"base64Encode", []string{"[]byte{1, 2, 3}"}}, 253 {"base64Decode", []string{"[]byte{1, 2, 3}"}}, 254 {"base58Encode", []string{"[]byte{1, 2, 3}"}}, 255 {"base58Decode", []string{"[]byte{1, 2, 3}"}}, 256 {"base58CheckEncode", []string{"[]byte{1, 2, 3}"}}, 257 {"base58CheckDecode", []string{"[]byte{1, 2, 3}"}}, 258 {"itoa", []string{"4", "10"}}, 259 {"itoa10", []string{"4"}}, 260 {"atoi", []string{`"4"`, "10"}}, 261 {"atoi10", []string{`"4"`}}, 262 {"memoryCompare", []string{"[]byte{1}", "[]byte{2}"}}, 263 {"memorySearch", []string{"[]byte{1}", "[]byte{2}"}}, 264 {"memorySearchIndex", []string{"[]byte{1}", "[]byte{2}", "3"}}, 265 {"memorySearchLastIndex", []string{"[]byte{1}", "[]byte{2}", "3"}}, 266 {"stringSplit", []string{`"a,b"`, `","`}}, 267 {"stringSplitNonEmpty", []string{`"a,b"`, `","`}}, 268 }) 269 } 270 271 func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, nativeTestCases []nativeTestCase) { 272 srcBuilder := bytes.NewBuffer([]byte(`package foo 273 import "github.com/nspcc-dev/neo-go/pkg/interop/native/` + name + `" 274 import "github.com/nspcc-dev/neo-go/pkg/interop" 275 var _ interop.Hash160 276 `)) 277 for i, tc := range nativeTestCases { 278 addNativeTestCase(t, srcBuilder, ctr, i, name, tc.method, tc.params...) 279 } 280 281 ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil) 282 require.NoError(t, err) 283 284 t.Run(ctr.Name, func(t *testing.T) { 285 for i, tc := range nativeTestCases { 286 t.Run(tc.method, func(t *testing.T) { 287 runNativeTestCase(t, ne, di, ctr, i, tc.method, tc.params...) 288 }) 289 } 290 }) 291 } 292 293 func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []string) interop.HFSpecificMethodAndPrice { 294 paramLen := len(params) 295 296 switch { 297 case name == "itoa10" || name == "atoi10": 298 name = name[:4] 299 case strings.HasPrefix(name, "memorySearch"): 300 if strings.HasSuffix(name, "LastIndex") { 301 paramLen++ // true should be appended inside of an interop 302 } 303 name = "memorySearch" 304 case strings.HasPrefix(name, "stringSplit"): 305 if strings.HasSuffix(name, "NonEmpty") { 306 paramLen++ // true should be appended inside of an interop 307 } 308 name = "stringSplit" 309 default: 310 name = strings.TrimSuffix(name, "WithData") 311 } 312 313 latestHF := config.LatestHardfork() 314 cMD := ctr.HFSpecificContractMD(&latestHF) 315 md, ok := cMD.GetMethod(name, paramLen) 316 require.True(t, ok, cMD.Manifest.Name, name, paramLen) 317 return md 318 } 319 320 func addNativeTestCase(t *testing.T, srcBuilder *bytes.Buffer, ctr interop.ContractMD, i int, name, method string, params ...string) { 321 md := getMethod(t, ctr, method, params) 322 isVoid := md.MD.ReturnType == smartcontract.VoidType 323 srcBuilder.WriteString("func F" + strconv.Itoa(i) + "() ") 324 if !isVoid { 325 srcBuilder.WriteString("any { return ") 326 } else { 327 srcBuilder.WriteString("{ ") 328 } 329 methodUpper := strings.ToUpper(method[:1]) + method[1:] // ASCII only 330 methodUpper = strings.ReplaceAll(methodUpper, "Gas", "GAS") 331 methodUpper = strings.ReplaceAll(methodUpper, "Json", "JSON") 332 methodUpper = strings.ReplaceAll(methodUpper, "Id", "ID") 333 srcBuilder.WriteString(name) 334 srcBuilder.WriteRune('.') 335 srcBuilder.WriteString(methodUpper) 336 srcBuilder.WriteRune('(') 337 srcBuilder.WriteString(strings.Join(params, ", ")) 338 srcBuilder.WriteString(") }\n") 339 } 340 341 func runNativeTestCase(t *testing.T, b *nef.File, di *compiler.DebugInfo, ctr interop.ContractMD, i int, method string, params ...string) { 342 md := getMethod(t, ctr, method, params) 343 result := getTestStackItem(md.MD.ReturnType) 344 isVoid := md.MD.ReturnType == smartcontract.VoidType 345 346 v := vm.New() 347 v.LoadToken = func(id int32) error { 348 t := b.Tokens[id] 349 if t.Hash != ctr.Hash { 350 return fmt.Errorf("wrong hash %s", t.Hash.StringLE()) 351 } 352 if t.Method != md.MD.Name { 353 return fmt.Errorf("wrong name %s", t.Method) 354 } 355 if int(t.ParamCount) != len(md.MD.Parameters) { 356 return fmt.Errorf("wrong number of parameters %v", t.ParamCount) 357 } 358 if t.HasReturn != !isVoid { 359 return fmt.Errorf("wrong hasReturn %v", t.HasReturn) 360 } 361 if t.CallFlag != md.RequiredFlags { 362 return fmt.Errorf("wrong flags %v", t.CallFlag) 363 } 364 for i := 0; i < int(t.ParamCount); i++ { 365 _ = v.Estack().Pop() 366 } 367 if v.Estack().Len() != 0 { 368 return errors.New("excessive parameters on the stack") 369 } 370 if !isVoid { 371 v.Estack().PushVal(result) 372 } 373 return nil 374 } 375 invokeMethod(t, fmt.Sprintf("F%d", i), b.Script, v, di) 376 require.NoError(t, v.Run()) 377 if isVoid { 378 require.Equal(t, 0, v.Estack().Len()) 379 return 380 } 381 require.Equal(t, 1, v.Estack().Len(), "stack contains unexpected items") 382 require.Equal(t, result.Value(), v.Estack().Pop().Item().Value()) 383 } 384 385 func getTestStackItem(typ smartcontract.ParamType) stackitem.Item { 386 switch typ { 387 case smartcontract.AnyType, smartcontract.VoidType: 388 return stackitem.Null{} 389 case smartcontract.BoolType: 390 return stackitem.NewBool(true) 391 case smartcontract.IntegerType: 392 return stackitem.NewBigInteger(big.NewInt(42)) 393 case smartcontract.ByteArrayType, smartcontract.StringType, smartcontract.Hash160Type, 394 smartcontract.Hash256Type, smartcontract.PublicKeyType, smartcontract.SignatureType: 395 return stackitem.NewByteArray([]byte("result")) 396 case smartcontract.ArrayType: 397 return stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true), stackitem.Null{}}) 398 case smartcontract.MapType: 399 return stackitem.NewMapWithValue([]stackitem.MapElement{{ 400 Key: stackitem.NewByteArray([]byte{1, 2, 3}), 401 Value: stackitem.NewByteArray([]byte{5, 6, 7}), 402 }}) 403 case smartcontract.InteropInterfaceType: 404 return stackitem.NewInterop(42) 405 default: 406 panic("unexpected type") 407 } 408 }