diff --git a/README.md b/README.md index f2a978e42..af23dff9a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ A complete toolkit for the NEO blockchain, including: - [CLI tool](docs/cli.md) - [Smart contract compiler](docs/compiler.md) - [NEO virtual machine](docs/vm.md) +- [Smart contract examples](examples/README.md) This branch (**master**) is under active development now (read: won't work out of the box) and aims to be compatible with Neo 3. For the current stable @@ -116,6 +117,9 @@ and private network. For details on how Go code is translated to Neo VM bytecode and what you can and can not do in smart contract please refer to the [compiler documentation](docs/compiler.md). +Refer to [examples](examples/README.md) for more NEO smart contract examples +written in Go. + ## Wallets NeoGo differs substantially from C# implementation in its approach to diff --git a/cli/contract_test.go b/cli/contract_test.go index 87fee7919..31cab9456 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -475,6 +475,11 @@ func TestCompileExamples(t *testing.T) { e := newExecutor(t, false) for _, info := range infos { + if !info.IsDir() { + // example smart contracts are located in the `/examples` subdirectories, but + // there are also a couple of files inside the `/examples` which doesn't need to be compiled + continue + } t.Run(info.Name(), func(t *testing.T) { infos, err := ioutil.ReadDir(path.Join(examplePath, info.Name())) require.NoError(t, err) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..362aa9318 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,57 @@ +# NEO-GO smart contract examples + +`examples` directory contains smart contract examples written in Go. +These examples are aimed to demonstrate the basic usage of Go programming +language to write NEO smart contracts as far as to provide a brief introduction +to the NEO-specific interop package application. + +## Examples structure + +Each example represents a separate folder with the smart contract content inside. +The content is presented by the smart contract code written in Go and the smart +contract configuration file with `.yml` extension. + +Some contracts have a contract owner, which is needed for witness checking and +represented by the `owner` string constant inside the smart contract code. The +owner account address is taken from the [my_wallet.json](my_wallet.json). The +wallet is located under the `examples` directory, has a single account inside +with the `my_account` label which can be decrypted with the `qwerty` password. +You can use `my_wallet.json` to deploy example contracts. + +See the table below for the detailed examples description. + +| Example | Description | +| --- | --- | +| [engine](engine) | This contract demonstrates how to use `runtime` interop package which implements an API for `System.Runtime.*` NEO system calls. Please, refer to the `runtime` [package documentation](../pkg/interop/doc.go) for details. | +| [events](events) | The contract shows how execution notifications with the different arguments types can be sent with the help of `runtime.Notify` function of the `runtime` interop package. Please, refer to the `runtime.Notify` [function documentation](../pkg/interop/runtime/runtime.go) for details. | +| [iterator](iterator) | This example describes a way to work with NEO iterators. Please, refer to the `iterator` [package documentation](../pkg/interop/iterator/iterator.go) for details. | +| [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). | +| [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). | +| [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) | +| [token](token) | This contract implements NEP17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. | +| [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. | + +## Compile + +Please, refer to the neo-go +[smart contract compiler documentation](../docs/compiler.md) to compile example +smart contracts. + +## Deploy + +You can set up neo-go private network to deploy the example contracts. Please, +refer to the [consensus documentation](../docs/consensus.md) to start your own +privnet with neo-go nodes. + +To deploy smart contracts, refer to the +[Deploying section](../docs/compiler.md#deploying) of the compiler documentation. + +## Where to start + +Feel free to explore neo-go smart contract development +[workshop](https://github.com/nspcc-dev/neo-go-sc-wrkshp) to get the basic +concepts of how to develop, compile, debug and deploy NEO smart contracts written +in go. + + + diff --git a/examples/my_wallet.json b/examples/my_wallet.json new file mode 100644 index 000000000..8740d8757 --- /dev/null +++ b/examples/my_wallet.json @@ -0,0 +1,30 @@ +{ + "version" : "3.0", + "scrypt" : { + "p" : 8, + "r" : 8, + "n" : 16384 + }, + "accounts" : [ + { + "isdefault" : false, + "contract" : { + "parameters" : [ + { + "name" : "parameter0", + "type" : "Signature" + } + ], + "script" : "DCEDhEhWuuSSNuCc7nLsxQhI8nFlt+UfY3oP0/UkYmdH7G5BdHR2qg==", + "deployed" : false + }, + "key" : "6PYUz1rNSwDf9ad1vxYbJyK93GrnnPBhr819HgSefMvgU1H9QxqVVCZQtN", + "lock" : false, + "label" : "my_account", + "address" : "NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S" + } + ], + "extra" : { + "Tokens" : null + } +} diff --git a/examples/runtime/runtime.go b/examples/runtime/runtime.go index aa0d04255..102bbf628 100644 --- a/examples/runtime/runtime.go +++ b/examples/runtime/runtime.go @@ -7,7 +7,7 @@ import ( var ( // Check if the invoker of the contract is the specified owner - owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt") + owner = util.FromAddress("NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S") trigger byte ) diff --git a/examples/timer/timer.go b/examples/timer/timer.go index 6cbc02d2f..acf27a910 100644 --- a/examples/timer/timer.go +++ b/examples/timer/timer.go @@ -16,7 +16,7 @@ var ( // ctx holds storage context for contract methods ctx storage.Context // Check if the invoker of the contract is the specified owner - owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt") + owner = util.FromAddress("NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S") // ticksKey is a storage key for ticks counter ticksKey = []byte("ticks") ) diff --git a/examples/token-sale/token_sale.go b/examples/token-sale/token_sale.go index d75cf32ad..314f3c891 100644 --- a/examples/token-sale/token_sale.go +++ b/examples/token-sale/token_sale.go @@ -13,7 +13,7 @@ const ( ) var ( - owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt") + owner = util.FromAddress("NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S") trigger byte token TokenConfig ctx storage.Context diff --git a/examples/token/token.go b/examples/token/token.go index f0a7eb09c..fcda064fa 100644 --- a/examples/token/token.go +++ b/examples/token/token.go @@ -13,7 +13,7 @@ const ( ) var ( - owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt") + owner = util.FromAddress("NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S") token nep17.Token ctx storage.Context ) diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index 7ea0e8f65..89223b56e 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -45,6 +45,11 @@ func TestCompiler(t *testing.T) { infos, err := ioutil.ReadDir(examplePath) require.NoError(t, err) for _, info := range infos { + if !info.IsDir() { + // example smart contracts are located in the `examplePath` subdirectories, but + // there are also a couple of files inside the `examplePath` which doesn't need to be compiled + continue + } infos, err := ioutil.ReadDir(path.Join(examplePath, info.Name())) require.NoError(t, err) require.False(t, len(infos) == 0, "detected smart contract folder with no contract in it") diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index 4f3854fc9..15194cfcd 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "os" + "path" "testing" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -197,3 +198,20 @@ func TestWalletGetChangeAddress(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, sh) } + +func TestWalletForExamples(t *testing.T) { + const ( + examplesDir = "../../examples" + walletFile = "my_wallet.json" + walletPass = "qwerty" + accountLabel = "my_account" + ) + w, err := NewWalletFromFile(path.Join(examplesDir, walletFile)) + require.NoError(t, err) + require.Equal(t, 1, len(w.Accounts)) + require.Equal(t, accountLabel, w.Accounts[0].Label) + require.NoError(t, w.Accounts[0].Decrypt(walletPass)) + + // we need to keep the owner of the example contracts the same as the wallet account + require.Equal(t, "NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S", w.Accounts[0].Address, "need to change `owner` in the example contracts") +}