From 626eff584104b3531b4079257488990b36ecdf89 Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 16 May 2018 21:47:46 +0800 Subject: [PATCH] nvm: add NRC721BasicToken smart contract. --- nf/nvm/engine_v8_test.go | 100 ++++++++++++++ nf/nvm/test/NRC721BasicToken.js | 224 ++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 nf/nvm/test/NRC721BasicToken.js diff --git a/nf/nvm/engine_v8_test.go b/nf/nvm/engine_v8_test.go index f1b9e556e..e91784520 100644 --- a/nf/nvm/engine_v8_test.go +++ b/nf/nvm/engine_v8_test.go @@ -1066,6 +1066,106 @@ func TestNRC20ContractMultitimes(t *testing.T) { } } +func TestNRC721Contract(t *testing.T) { + + tests := []struct { + test string + contractPath string + sourceType string + name string + symbol string + from string + to string + tokenID string + }{ + {"nrc721", "./test/NRC721BasicToken.js", "js", "721标准代币", "ST", + "n1FkntVUMPAsESuCAAPK711omQk19JotBjM", "n1Kjom3J4KPsHKKzZ2xtt8Lc9W5pRDjeLcW", "1001", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := ioutil.ReadFile(tt.contractPath) + assert.Nil(t, err, "contract path read error") + + mem, _ := storage.NewMemoryStorage() + context, _ := state.NewWorldState(dpos.NewDpos(), mem) + assert.Nil(t, err) + + // prepare the contract. + contract, _ := context.CreateContractAccount([]byte("account2"), nil) + contract.AddBalance(newUint128FromIntWrapper(5)) + + // parepare env, block & transactions. + tx := mockNormalTransaction(tt.from, "n1TV3sU6jyzR4rJ1D7jCAmtVGSntJagXZHC", "0") + ctx, err := NewContext(mockBlock(), tx, contract, context) + + // execute. + engine := NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + args := fmt.Sprintf("[\"%s\", \"%s\"]", tt.name, tt.symbol) + _, err = engine.DeployAndInit(string(data), tt.sourceType, args) + assert.Nil(t, err) + engine.Dispose() + + // call name. + engine = NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + name, err := engine.Call(string(data), tt.sourceType, "name", "") + assert.Nil(t, err) + var nameStr string + err = json.Unmarshal([]byte(name), &nameStr) + assert.Nil(t, err) + assert.Equal(t, tt.name, nameStr) + engine.Dispose() + + // call symbol. + engine = NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + symbol, err := engine.Call(string(data), tt.sourceType, "symbol", "") + assert.Nil(t, err) + var symbolStr string + err = json.Unmarshal([]byte(symbol), &symbolStr) + assert.Nil(t, err) + assert.Equal(t, tt.symbol, symbolStr) + assert.Nil(t, err) + engine.Dispose() + + // call mint. + engine = NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + mintArgs := fmt.Sprintf("[\"%s\", \"%s\"]", tt.from, tt.tokenID) + result, err := engine.Call(string(data), tt.sourceType, "mint", mintArgs) + assert.Nil(t, err) + assert.Equal(t, "\"\"", result) + engine.Dispose() + + // call approve. + engine = NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + approveArgs := fmt.Sprintf("[\"%s\", \"%s\"]", tt.to, tt.tokenID) + result, err = engine.Call(string(data), tt.sourceType, "approve", approveArgs) + assert.Nil(t, err) + assert.Equal(t, "\"\"", result) + engine.Dispose() + + // parepare env, block & transactions. + tx = mockNormalTransaction(tt.to, "n1TV3sU6jyzR4rJ1D7jCAmtVGSntJagXZHC", "0") + ctx, err = NewContext(mockBlock(), tx, contract, context) + + // call transferFrom. + engine = NewV8Engine(ctx) + engine.SetExecutionLimits(10000, 100000000) + transferFromArgs := fmt.Sprintf("[\"%s\", \"%s\", \"%s\"]", tt.from, tt.to, tt.tokenID) + result, err = engine.Call(string(data), tt.sourceType, "transferFrom", transferFromArgs) + assert.Nil(t, err) + assert.Equal(t, "\"\"", result) + engine.Dispose() + + }) + } + } + func TestNebulasContract(t *testing.T) { tests := []struct { name string diff --git a/nf/nvm/test/NRC721BasicToken.js b/nf/nvm/test/NRC721BasicToken.js new file mode 100644 index 000000000..051ab496c --- /dev/null +++ b/nf/nvm/test/NRC721BasicToken.js @@ -0,0 +1,224 @@ +// Copyright (C) 2017 go-nebulas authors +// +// This file is part of the go-nebulas library. +// +// the go-nebulas library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// the go-nebulas library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the go-nebulas library. If not, see . +// + +'use strict'; + +var Operator = function (obj) { + this.operator = {}; + this.parse(obj); +}; + +Operator.prototype = { + toString: function () { + return JSON.stringify(this.operator); + }, + + parse: function (obj) { + if (typeof obj != "undefined") { + var data = JSON.parse(obj); + for (var key in data) { + this.operator[key] = data[key]; + } + } + }, + + get: function (key) { + return this.operator[key]; + }, + + set: function (key, value) { + this.operator[key] = value; + } +}; + +var StandardToken = function () { + LocalContractStorage.defineProperties(this, { + _name: null, + _symbol: null, + }); + + LocalContractStorage.defineMapProperties(this, { + "tokenOwner": null, + "ownedTokensCount": { + parse: function (value) { + return new BigNumber(value); + }, + stringify: function (o) { + return o.toString(10); + } + }, + "tokenApprovals": null, + "operatorApprovals": { + parse: function (value) { + return new Operator(value); + }, + stringify: function (o) { + return o.toString(); + } + }, + + }); +}; + +StandardToken.prototype = { + init: function (name, symbol) { + this._name = name; + this._symbol = symbol; + }, + + name: function () { + return this._name; + }, + + symbol: function () { + return this._symbol; + }, + + balanceOf: function (_owner) { + var balance = this.ownedTokensCount.get(_owner); + if (balance instanceof BigNumber) { + return balance.toString(10); + } else { + return "0"; + } + }, + + ownerOf: function (_tokenId) { + return this.tokenOwner.get(_tokenId); + }, + + approve: function (_to, _tokenId) { + var from = Blockchain.transaction.from; + + var owner = this.ownerOf(_tokenId); + if (_to == owner) { + throw new Error("invalid address in approve."); + } + // msg.sender == owner || isApprovedForAll(owner, msg.sender) + if (owner == from || this.isApprovedForAll(owner, from)) { + this.tokenApprovals.set(_tokenId, _to); + this.approveEvent(true, owner, _to, _tokenId); + } else { + throw new Error("permission denied in approve."); + } + }, + + getApproved: function (_tokenId) { + return this.tokenApprovals.get(_tokenId); + }, + + setApprovalForAll: function(_to, _approved) { + var from = Blockchain.transaction.from; + if (from == _to) { + throw new Error("invalid address in setApprovalForAll."); + } + var operator = this.operatorApprovals.get(from) || new Operator(); + operator.set(_to, _approved); + this.operatorApprovals.set(from, operator); + }, + + isApprovedForAll: function(_owner, _operator) { + var operator = this.operatorApprovals.get(_owner); + if (operator != null) { + if (operator.get(_operator) === "true") { + return true; + } else { + return false; + } + } + }, + + isApprovedOrOwner: function(_spender, _tokenId) { + var owner = this.ownerOf(_tokenId); + return _spender == owner || this.getApproved(_tokenId) == _spender || this.isApprovedForAll(owner, _spender); + }, + + transferFrom: function (_from, _to, _tokenId) { + var from = Blockchain.transaction.from; + if (this.isApprovedOrOwner(from, _tokenId)) { + this.clearApproval(_from, _tokenId); + this.removeTokenFrom(_from, _tokenId); + this.addTokenTo(_to, _tokenId); + this.transferEvent(true, _from, _to, _tokenId); + } else { + throw new Error("permission denied in transferFrom."); + } + + }, + + clearApproval: function (_owner, _tokenId) { + var owner = this.ownerOf(_tokenId); + if (_owner != owner) { + throw new Error("permission denied in clearApproval."); + } + this.tokenApprovals.del(_tokenId); + }, + + removeTokenFrom: function(_from, _tokenId) { + if (_from != this.ownerOf(_tokenId)) { + throw new Error("permission denied in removeTokenFrom."); + } + var tokenCount = this.ownedTokensCount.get(_from); + if (tokenCount.lt(1)) { + throw new Error("Insufficient account balance in removeTokenFrom."); + } + this.ownedTokensCount.set(_from, tokenCount-1); + }, + + addTokenTo: function(_to, _tokenId) { + this.tokenOwner.set(_tokenId, _to); + var tokenCount = this.ownedTokensCount.get(_to) || new BigNumber(0); + this.ownedTokensCount.set(_to, tokenCount+1); + }, + + mint: function(_to, _tokenId) { + this.addTokenTo(_to, _tokenId); + this.transferEvent(true, "", _to, _tokenId); + }, + + burn: function(_owner, _tokenId) { + this.clearApproval(_owner, _tokenId); + this.removeTokenFrom(_owner, _tokenId); + this.transferEvent(true, _owner, "", _tokenId); + }, + + transferEvent: function (status, _from, _to, _tokenId) { + Event.Trigger(this.name(), { + Status: status, + Transfer: { + from: _from, + to: _to, + tokenId: _tokenId + } + }); + }, + + approveEvent: function (status, _owner, _spender, _tokenId) { + Event.Trigger(this.name(), { + Status: status, + Approve: { + owner: _owner, + spender: _spender, + tokenId: _tokenId + } + }); + } + +}; + +module.exports = StandardToken;