1 Star 0 Fork 0

binny1024 / truffle-demo

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Unlicense

一 参考资料

一 环境搭建

1.1 install ganache

Install and start Ganache as a local Ethereum test node.

1.2 Install truffle

npm install -g truffle

1.3 Install Nodejs dependencies

npm install -g solc solc-cli --save-dev

二 truffle 命令

2.1 编译命令

  1. Compile contracts
truffle compile

带参数 --all

truffle compile --all

2.2 部署

Run migrations

truffle migrate

带参数 --reset

truffle migrate --reset

输出到文件

sudo truffle migrate --reset >> ./log/migrate.log && echo "--------add payable----" >>./log/migrate.log 

重新部署,就是发一个新的合约,旧的合约还在链上

三 合约语法

3.1 数据类型

3.1.1 address

以太坊地址,20 个字节,使用uint160来存储的,而且他们之间可以相互强制转换:

 address payable ap = address(uint160(addr));

地址之间是可以直接进行比较大小:<=, <, >= , >,==, !=

地址类型有两种形式,他们大致相同:

  • address:保存一个20字节的值(以太坊地址的大小)。
  • address payable :可支付地址,与 address 相同,不过有成员函数 transfer 和 send 。
image-20201223183101124

3.1.2 整型

int/uint 变长的有符号或无符号整形。支持以8递增,uint8到uint256,uint默认为uint256。

3.1.3 布尔型

3.1.4 映射

键值对映射,例如:mapping(address => uint)

3.1.5 结构体

struct Solidity {
	address addr;
  uint amount;
}

3.2 存储

三种类型:storagememorycalldata

默认存储类型

  • 变量定义:storage
  • 函数参数及返回值:memory

3.2.1 storage

永久存储,费用相对较高。

3.2.2 memory

临时变量存储,可以理解为栈空间

3.3 访问权限

合约与合约之间以及合约与其子类之间的访问权限。类比 Java 的publicprotectedprivate,用法相似,略有不同。

image-20201223181546436

3.3.1 public

该修饰符修饰变量既可以被 internal 方式的访问,也可以通过 external 方式的调用。可以理解为既能够被内部合约访问,也能被外部合约调用。

3.3.2 external

能够被外部合约调用

3.3.3 internal

只允许通过 internal 方式调用,不能被外部合约调用。

3.3.3 private

internal一样,都不能被外部合约访问,唯一不同的是 private函数不能被子类调用。

3.3.4 constant、pure、view

Solidity v4.17之前.只有 constant,后来有人嫌 constant这个词本身代表变量中的常量,不适合用来修饰函数,所以将constant拆成了viewpure

当函数有返回值时,可以添加这三种定义.,用这三种方式定 义的函数都只执行读操作,不会进行编译执行。即用了这三 种方式定义的函数,不会执行函数里的逻辑.只会执行一个返回的读操作。所以执行这些函数不需要消耗gas费用。

pure 则更为严格,pure 修饰的函数不能改也不能读变量状态,否则编译通不过。

view 的作用和 constant一模一样,可以读取状态变量但是不能改。

image-20201223182113493

3.4 函数修改器

相当于自定义函数的修饰符。

修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。

image-20201223182520591

3.5 回退函数

fallback function回退函数,每一个合约有且仅有一个没有名字的函数,往合约发送消息时,会执行该函数。如果合约要正常接受ether,需要加上payable声明。声明后,当有用户往合约转账时,将触发该函数,可以在里面写相应的逻辑。

  • 1.当外部账户或其他合约向该合约地址发送 ether 时;
  • 2.当外部账户或其他合约调用了该合约一个不存在的函数时
image-20201223182701971

3.6 异常处理

使用状态恢复来处理异常,就是说当抛出异常时将恢复到调用前的状态。抛出异常的方式有assert,require,revert,throw。

3.6.1 assert

assert函数,用于条件检查,只能测试内部错误和检查常量。

3.6.1 require

require函数,也是用于条件检查,用于测试调用的输入或者合约状态变量。

3.6.3 revert

revert 函数用于标记错误并恢复当前调用。

3.6.4 throw

throwrevert 一样,但是throw在0.4.13被弃用,将来会被淘汰。

pragma solidity >=0.4.22 <0.6.0;

contract HelloWorld {
    //检查内部计算是否会溢出
    function add(uint256 a, uint256 b) internal pure returns (uint256){
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }

    function send() payable public returns (uint balance){
        require(msg.value % 2 == 0);
        //只允许偶数
        return msg.value;
        msg.value;}

    function buy(uint amount) payable public {
        if (amount > msg.value / 2 ether) {
            revert("not enough ether provided.");
        }
    }
}

3.7 合约相关

3.7.1 this

this(当前合约的类型):表示当前合约,可以显式的转换为 Address

3.7.2 selfdestruct

selfdestruct(address recipient):销毁当前合约,并把它所有资金发送到给定的地址。

3.8 几种转币方法对比

pragma solidity >=0.4.22<0.6.0;

contract Helloworld {
    address payable public winner;

    function sendTowinner(uint256 winAmount) public {
        winner.send(winAmount);
        winner.transfer(winAmount);
        winner.call.value(winAmount);
    }
}

3.8.1 transfer()

<address>.transfer()

  • 当发送失败时会 throw; 回滚状态;
  • 只会传递 2300 Gas 供调用,防止重入攻击;

3.8.2 send()

<address>.send()

  • 当发送失败时会返回 false 布尔值;

  • 只会传递 2300 Gas 供调用,防止重入;

3.8.3 gas().call.value()

<address>.gas().call.value()

  • 当发送失败时会返回 false 布尔值;
  • 传递所有可用 Gas 进行调用(可通过 gas(gas_value) 进行限制),不能有效防止重入;

3.9 常用 API

3.9.1 区块相关

  • block.blockhash(uint blockNumber) returns (bytes32):返回给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。
  • block.coinbase (address): 当前块矿工的地址。
  • block.difficulty (uint):当前块的难度。
  • block.gaslimit (uint):当前块的gaslimit。
  • block.number (uint):当前区块的块号。
  • block.timestamp (uint): 当前块的Unix时间戳(从1970/1/1 00:00:00 UTC开始所经过的秒数)

3.9.2 调用者相关

  • msg.sender (address): 当前调用发起人的地址。
  • msg.value (uint): 这个消息所附带的以太币,单位为wei。
  • msg.sig (bytes4):调用数据(calldata)的前四个字节(例如为:函数标识符)。
  • msg.data (bytes): 完整的调用数据(calldata)。
  • tx.origin (address): 交易的发送者
  • tx.gasprice (uint) : 交易的 gas 价格。
  • now (uint): 当前块的时间戳 (block.timestamp 的别名)

四 truffle

https://learnblockchain.cn/docs/truffle/reference/configuration.html#id2

4.1 目录结构

  • contracts:智能合约目录
  • migrations:部署文件的 js 脚本目录
  • test:测试用例目录
  • truffle-config.js:truffle 的配置文件

4.2 依赖

可以使用 import 关键字声明依赖

  • 通过文件路径
import "./AnotherContract.sol";
  • 通过外包
import "somepackage/SomeContract.sol";

4.3 编译合约

  • 命令
truffle compile [--all]

每次编译只会涉及上一次修改后的代码,如果要强制重新编译,可以使用 —all选项.编译结果保存在 build/contracts/文件夹中.

4.4 部署脚本

部署脚本在 migrations 文件夹下

  • 命令
truffle migrate [--reset]
truffle migrate --network live

Migrations.sol合约会记录部署的历史,如果需要忽略部署历史,可以使用--reset参数

4.4.1 部署文件

const Migrations = artifacts.require("Migrations");

module.exports = function(deployer) {
  deployer.deploy(Migrations);
};

artifacts.require

使用该方法获取合约的实例,传入的参数名应该与合约代码定义的名字一致(不是文件名).

contract ContractOne{
		//...
}
contract ContractTwo{
		//...
}

如果只是用使用其中一个合约,可以是这样的:

var ContractTwo = artifacts.require("ContractTwo");

如果只是用使用两个合约,可以是这样的:

var ContractOne = artifacts.require("ContractOne");
var ContractTwo = artifacts.require("ContractTwo");

module.exports

部署脚本的函数必须用module.exports语法兼容truffle框架,truffle在函数中注入了一个deployer对象,deployer对象提供了部署合约所需的所有API。

4.4.2 部署

  • Deploy A,then deploy B,passing in A`s newly address deployed address
deployer.deploy(A).then(
		function(){
			return deployer.deploy(B,A.address);
		}
);
  • network-beased
module.exports=function(deployer,network){
	if (network=="live"){
		//Do something specific to the network named "live".
	}else{
		//Perform a different step otherwise.
	}
}
  • with accoints
module.exports = function(deployer,network,accounts){
	// use the accounts whitin your migrations
}

4.5 部署案例

4.5.1 合约源码

pragma solidity ^0.6.11;

contract SimpleStorage {
    int _age;
    constructor () public {
    }
    function setAge(int age) public {
        _age = age;
    }

    function getAge() public view returns (int){
        return _age;
    }
}

4.5.2 迁移脚本

const SimpleStorage = artifacts.require("SimpleStorage");

module.exports = function(deployer) {
    deployer.deploy(SimpleStorage);
};

4.5.3 编译合约

truffle compile

4.5.4 部署

truffle migrate

五 truffle 测试框架

truffle 使用 Mocha测试框架和 Chai 断言为我们提供一个简便的,稳固的JS版本的测试框架,使用 **contract()**替代了 describe().

结构上来说,我们的测试基本上遵循 Mocha的测试风格:

  • 测试脚本必须存放在 ./test目录中.
  • 必须以 .js为扩展名.
  • 必须被 Mocha识别.

Mocha 不同的是 **contract()**函数,该函数跟 describe() 非常像,不同之处在于:

  • 每次 **contract()**函数运行之前,智能合约都会被重新部署并运行在以太坊节点上,以保证测试用例相关的合约状态的纯净.
  • 此外, **contract()**函数还提供了一些列的以太坊客户端提供的测试账号.供我们在测试用例中使用.

当然,在我们不需要一个纯净的测试合约状态的时候,依然可以使用 describe().

5.1 artifacts.require

由于 Truffle没有方法检测到我们将使用哪一个合约进行交互式测试, **Contract abstractions **使得 js可以与智能合约交互.可以使用 truffle 提供的 artifacts.require("")方法.

在测试脚本中使用artifacts.require("")就如同在迁移脚本中使用一样,

5.2 web3

web3 实例在每一个测试文件中都是可用的,可以直接使用 web3.eth.getBalance.

5.3 使用 then

  • ./test/metacoin.js
const MetaCoin = artifacts.require("MetaCoin");

contract("MetaCoin", accounts => {
  it("should put 10000 MetaCoin in the first account", () =>
    MetaCoin.deployed()
      .then(instance => instance.getBalance.call(accounts[0]))
      .then(balance => {
        assert.equal(
          balance.valueOf(),
          10000,
          "10000 wasn't in the first account"
        );
      }));

  it("should call a function that depends on a linked library", () => {
    let meta;
    let metaCoinBalance;
    let metaCoinEthBalance;

    return MetaCoin.deployed()
      .then(instance => {
        meta = instance;
        return meta.getBalance.call(accounts[0]);
      })
      .then(outCoinBalance => {
        metaCoinBalance = outCoinBalance.toNumber();
        return meta.getBalanceInEth.call(accounts[0]);
      })
      .then(outCoinBalanceEth => {
        metaCoinEthBalance = outCoinBalanceEth.toNumber();
      })
      .then(() => {
        assert.equal(
          metaCoinEthBalance,
          2 * metaCoinBalance,
          "Library function returned unexpected function, linkage may be broken"
        );
      });
  });

  it("should send coin correctly", () => {
    let meta;

    // Get initial balances of first and second account.
    const account_one = accounts[0];
    const account_two = accounts[1];

    let account_one_starting_balance;
    let account_two_starting_balance;
    let account_one_ending_balance;
    let account_two_ending_balance;

    const amount = 10;

    return MetaCoin.deployed()
      .then(instance => {
        meta = instance;
        return meta.getBalance.call(account_one);
      })
      .then(balance => {
        account_one_starting_balance = balance.toNumber();
        return meta.getBalance.call(account_two);
      })
      .then(balance => {
        account_two_starting_balance = balance.toNumber();
        return meta.sendCoin(account_two, amount, { from: account_one });
      })
      .then(() => meta.getBalance.call(account_one))
      .then(balance => {
        account_one_ending_balance = balance.toNumber();
        return meta.getBalance.call(account_two);
      })
      .then(balance => {
        account_two_ending_balance = balance.toNumber();

        assert.equal(
          account_one_ending_balance,
          account_one_starting_balance - amount,
          "Amount wasn't correctly taken from the sender"
        );
        assert.equal(
          account_two_ending_balance,
          account_two_starting_balance + amount,
          "Amount wasn't correctly sent to the receiver"
        );
      });
  });
});
  • output
 Contract: MetaCoin
    √ should put 10000 MetaCoin in the first account (83ms)
    √ should call a function that depends on a linked library (43ms)
    √ should send coin correctly (122ms)


  3 passing (293ms)

5.1.4 使用 async-await

const MetaCoin = artifacts.require("MetaCoin");

contract("2nd MetaCoin test", async accounts => {
  it("should put 10000 MetaCoin in the first account", async () => {
    let instance = await MetaCoin.deployed();
    let balance = await instance.getBalance.call(accounts[0]);
    assert.equal(balance.valueOf(), 10000);
  });

  it("should call a function that depends on a linked library", async () => {
    let meta = await MetaCoin.deployed();
    let outCoinBalance = await meta.getBalance.call(accounts[0]);
    let metaCoinBalance = outCoinBalance.toNumber();
    let outCoinBalanceEth = await meta.getBalanceInEth.call(accounts[0]);
    let metaCoinEthBalance = outCoinBalanceEth.toNumber();
    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance);
  });

  it("should send coin correctly", async () => {
    // Get initial balances of first and second account.
    let account_one = accounts[0];
    let account_two = accounts[1];

    let amount = 10;

    let instance = await MetaCoin.deployed();
    let meta = instance;

    let balance = await meta.getBalance.call(account_one);
    let account_one_starting_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_starting_balance = balance.toNumber();
    await meta.sendCoin(account_two, amount, { from: account_one });

    balance = await meta.getBalance.call(account_one);
    let account_one_ending_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_ending_balance = balance.toNumber();

    assert.equal(
      account_one_ending_balance,
      account_one_starting_balance - amount,
      "Amount wasn't correctly taken from the sender"
    );
    assert.equal(
      account_two_ending_balance,
      account_two_starting_balance + amount,
      "Amount wasn't correctly sent to the receiver"
    );
  });
});

5.1.5 运行测试

truffle test ./test/metacoin.js

结合 contract(),每次执行上述命令都会重新部署合约(貌似不会上链).

六 基于 eth的测试代码

不使用 truffle 提供的测试框架 而是基于以太坊接口,来编写测试代码调试合约.

接口文档:https://web3js.readthedocs.io/en/v1.3.1/web3-eth-contract.html

6.1 合约对象

使用 web3.eth.Contract 对象与以太坊上的智能合约交互

    Contract: new (
        jsonInterface: AbiItem[] | AbiItem,
        address?: string,
        options?: ContractOptions
    ) => Contract;
参数 含义
jsonInterface The json interface for the contract to instantiate
address The address of the smart contract to call.
options The options of the contract. Some are used as fallbacks for calls and transactions
返回值 含义
Object The contract instance with all its methods and events
  • ContractOptions
export interface ContractOptions {
    // Sender to use for contract calls,
    //The address transactions should be made from.
    from?: string;
    // Gas price to use for contract calls,
    // The gas price in wei to use for transactions.
    gasPrice?: string;
    // Gas to use for contract calls
    // The maximum gas provided for a transaction (gas limit).
    gas?: number;
    // Contract code
    // The byte code of the contract. Used when the contract gets deployed.
    data?: string;
} 
参数 含义
from The address transactions should be made from.
gasPrice The gas price in wei to use for transactions.
gas The maximum gas provided for a transaction (gas limit).
data The byte code of the contract. Used when the contract gets deployed.

使用方式:

var myContract = new web3.eth.Contract([...], '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe', {
    from: '0x1234567890123456789012345678901234567891', // default from address
    gasPrice: '20000000000' // default gas price in wei, 20 gwei in this case
});

6.2 属性

6.2.1 defaultAccount

  • Property
类型 含义
String 20 Bytes: Any ethereum address. You should have the private key for that address in your node or keystore. (Default is undefined)
  • 设置默认账户
web3.eth.defaultAccount = accounts[1];

以下方法在没有设置 from 属性的时候,该属性的值就会作为 from 的值来使用.

web3.eth.sendTransaction()
web3.eth.call()
new web3.eth.Contract() -> myContract.methods.myMethod().call()
new web3.eth.Contract() -> myContract.methods.myMethod().send()

6.2.2 options.address

The address used for this contract instance. All transactions generated by web3.js from this contract will contain this address as the "to".

  • Property
类型 含义 备注
String|null 合约地址 没有指定则为 null

6.2.3 defaultBlock

The default block is used for certain methods. You can override it by passing in the defaultBlock as last parameter. The default value is "latest”.

  • Property:The default block parameters can be one of the following:
取值 类型 备注
数值 Number|BN|BigNumber A block number
earliest String The genesis block
latest String The latest block (current head of the blockchain)
pending String The currently mined block (including pending transactions)

使用 NodeJs 进行合约的调试

数据的读写

transaction:写数据

  • 会改变以太坊的状态
  • 需要消耗 gas
  • 不会立即执行,需要等待区块确认
  • 不能定义返回值

call:读数据

  • 不会改变以太坊状态
  • 不需要消耗 gas
  • 立即执行
  • 能定义返回值

七 事件监听

如果合约需要监听业务,则可以使用 事件监听 来获取每次业务的执行.

const Web3 = require("web3");
const shelljs = require("shelljs");
(async () => {
    // 合约abi
    let loansAbi;
// 合约实例
    let instance;
// 链上账户信息
    let accounts;
// 合约地址,可以从链上查看或者从 Ganche 客户端查看
    const loansAddress = '0x31b9dE9548200E200EB3d32671169fB3b9c394af'
//创建一个 web3 示例
    const web3 = new Web3(new Web3.providers.WebsocketProvider('ws://localhost:7545'))
    accounts = await web3.eth.getAccounts();
    console.log(accounts);
    loansAbi = JSON.parse(shelljs.cat('./instance/abi.txt').stdout);
    instance = new web3.eth.Contract(loansAbi, loansAddress);
    // console.log(instance);

    /**
     * 此方式只输出一次事件
     */
    instance.getPastEvents('set_age',{fromBlock:168},function (error, result) {
        if (error) {
            console.error(error);
        } else {
            console.log(result)
        }
    });

    /**
     * 持续监听事件
     */
    instance.events.set_age( {fromBlock: 168}, function (error, result) {
        if (error) {
            console.error(error);
        } else {
            console.log(result)
        }
    })
})()

八 测试案例

https://gitee.com/xubinbin1024/solidity-demo.git

8.1 测试账号

three proof decorate oil solution injury knock weather proof copper join rabbit

8.2 案例一

8.2.1 合约代码

pragma solidity ^0.6.11;

contract SimpleStorage {
    int _age;
    constructor () public {
    }
    function setAge(int age) public {
        _age = age;
    }

    function getAge() public view returns (int){
        return _age;
    }
}

8.2.2 部署脚本

  • 2_simple_storage:文件名一定要有数字前缀
const SimpleStorage = artifacts.require("SimpleStorage");

module.exports = function(deployer) {
    deployer.deploy(SimpleStorage);
};

8.2.3 测试代码

8.2.3.1 truffle 测试框架

const SimpleStorage = artifacts.require("SimpleStorage");
contract("SimpleStorage", function (accounts) {
    it('所有账户信息', async function () {
        console.log(accounts);
    });
    it('合约实例 ', async function () {
        let instance = await SimpleStorage.deployed();
        console.log('合约地址', instance.address);
    });
    it('账户余额 ', async function () {
        let balance = await web3.eth.getBalance(accounts[0]);
        console.log(balance);
    });
});

1, 从以上代码的 合约实例的输出中,可以看出每次输出的合约地址是不同的,正如第五章所说,每次执行测试都会重新部署合约.

2, 可以在此这中测试代码中直接使用 web3对象.

8.2.3.2 基于 web3 接口

上述代码看似方便,但是不便于调试.

本测试代码使用单纯的 Mocha框架,也因此不会每次都重新部署合约.也便于调试.这种方式的特点:

  • 不会每次重新部署合约
  • 需要自己引入 web3对象,需要它的话
  • 对合约的操作理解会更透彻
const Web3 = require("web3")
const shelljs = require("shelljs");
/**
 * 用于离线签名
 * @type {Transaction}
 */
const Tx = require('ethereumjs-tx').Transaction;
/**
 * 合约地址:合约部署后,可以从 ganache 客户端获取
 * @type {number}
 */
const address = '0xAa51C85b57b65f46B1b37A95C42C0B4532b317a9'

describe("SimpleStorage", function () {
    let web3;
    it('get web3', async function () {
        web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:7545'))
    });
    let accounts;
    it('所有账户信息', async function () {
        accounts = await web3.eth.getAccounts();
        console.log(accounts);
    });
    it('检查地址的合法性', async function () {
        let myiban = web3.eth.Iban.fromAddress(address)
        console.log(myiban) 
    });
    let abi;
    it('合约abi ', async function () {
        abi = JSON.parse(shelljs.cat('./instance/abi.txt').stdout);
        console.log(abi);
    });
    let instance;
    it('获取合约实例', async function () {
        instance = new web3.eth.Contract(abi, address, {
            from: accounts[0]
        });
        shelljs.echo(instance).to('./instance/instance1.log');
    });
    let from;
    it('设置默认 from 账户', async function () {
        from = accounts[2];
        web3.eth.defaultAccount = from;
        console.log(from);
    });
    it('合约默认账户', async function () {
        console.log(web3.eth.Contract.defaultAccount);
    });
    it('address', async function () {
        console.log(instance.options.address);
    });
    it('账户余额 ', async function () {
        let balance = await web3.eth.getBalance(accounts[0]);
        console.log(balance);
    });
    it.skip('转账(在线签)', async function () {
        let transactionReceipt = await web3.eth.sendTransaction({
            to: accounts[0],
            value: web3.utils.toWei('1', 'ether')
        });
        console.log(transactionReceipt);
    });
    let nonce;
    it('当前账户的 nonce', async function () {
        nonce = await web3.eth.getTransactionCount(from);
        console.log(nonce);
    });
    const token_hex = '0x';
    it.skip('离线签', async function () {
        let priv = "69d3f76ddb441c41547caabcc5dbdaacedebc389ce0cc3eb3a240778384ee9a9";
        let privateKey = Buffer.from(priv, 'hex');
        let priceWei = await web3.eth.getGasPrice();
        priceWei = token_hex + Number(priceWei).toString(16);
        const gasLimitCounts = token_hex + Number(30000).toString(16);
        const amountWei = web3.utils.toWei('1', 'ether');
        // log('amountWei = ' + amountWei);
        const amount = token_hex + Number(amountWei).toString(16);
        let rawTx = {
            nonce: nonce,
            gasPrice: priceWei.toString(),
            gasLimit: gasLimitCounts.toString(),
            to: '0x0000000000000000000000000000000000000000',
            value: amount.toString(),
            data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057'
        };
        let tx = new Tx(rawTx);
        tx.sign(privateKey);
        let serializedTx = tx.serialize();
        let transactionReceipt = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'));
        console.log(transactionReceipt);
    });
    it('获取 from 余额', async function () {
        let balance = await web3.eth.getBalance(from);
        console.log(balance);
    });
    it('当前块高', async function () {
        let number = await web3.eth.getBlockNumber();
        console.log(number);
    });
    it('读数据', async function () {
        let age = await instance.methods.getAge().call();
        console.log(age);
    });
    it('写数据', async function () {
        let newVar = await instance.methods.setAge(10).send();
        console.log(newVar);
    });
    it('读数据', async function () {
        let age = await instance.methods.getAge().call();
        console.log(age);
    });
    it('获取 from 余额', async function () {
        let balance = await web3.eth.getBalance(from);
        console.log(balance);
    });
});

8.3 案例二

8.3.1 合约代码

pragma solidity >=0.5.0 <0.7.0;
/*
场景说明:
银行提供企业贷款服务,申请企业需要提交公司授信资质(法人征信正常 与否)以及其供应链上交易信息(是否有合约纠纷和违约逾期),
银行需要通过 银行人工审核和机审才可以给企业放款。机审时若碰到法人征信不正常、企业产 生供应链上有合约纠纷、违约逾期等情况,则判断审核不通过。
*/
contract Loans {
    address  company;//申请企业的地址
    address  owner;//合约的创建者,即审核人员的地址信息
    struct Application {
        bool credit;//法人征信是否正常
        bool dispute;//是否有合约纠纷
        bool offlineMac;//是否通过线下机审
        bool offlineMan;//是否通过线下人工审核
        bool overdue;//是否逾期
    }
    constructor () public {
        owner = msg.sender;
    }
    mapping(address => Application)   applicationInfo;

    event ApplicationSubmit(address addr, bool credit, bool dispute, bool overdue);
    //申请企业提交信息
    function applicationSubmit(bool credit, bool dispute, bool overdue) public {
        applicationInfo[msg.sender].credit = credit;
        applicationInfo[msg.sender].dispute = dispute;
        applicationInfo[msg.sender].overdue = overdue;
        emit ApplicationSubmit(msg.sender, credit, dispute, overdue);
    }

    event OffLineStuffSubmit(address addr, bool offlineMan, bool offlineMac);

    function offlineStuffSubmit(bool offlineMan, bool offlineMac, address addr) public {
        require(msg.sender == owner, 'you dont have access to do this');
        applicationInfo[addr].offlineMan = offlineMan;
        applicationInfo[addr].offlineMac = offlineMac;
        emit OffLineStuffSubmit(addr, offlineMan, offlineMac);
    }

    function getApplicationResult() public view returns (bool){
        if (applicationInfo[msg.sender].credit == true
        && applicationInfo[msg.sender].overdue == true
        && applicationInfo[msg.sender].offlineMac == true
        && applicationInfo[msg.sender].offlineMan == true
            && applicationInfo[msg.sender].dispute == true) {
            return true;
        }
        return false;
    }

    function getApplicationCredit() public view returns (bool){
        return applicationInfo[msg.sender].credit;
    }

    function getApplicationDispute() public view returns (bool){
        return applicationInfo[msg.sender].dispute;
    }

    function getApplicationOverdue() public view returns (bool){
        return applicationInfo[msg.sender].overdue;
    }

    function getApplicationMan() public view returns (bool){
        return applicationInfo[msg.sender].offlineMan;
    }

    function getApplicationMac() public view returns (bool){
        return applicationInfo[msg.sender].offlineMac;
    }

}

8.3.2 事件订阅

const Web3 = require("web3");

(async () => {
    // 合约abi
    let loansAbi;
// 合约实例
    let instance;
// 链上账户信息
    let accounts;
// 合约地址,可以从链上查看或者从 Ganche 客户端查看
    const loansAddress = '0x8ebce3764E3b0442af914C4b6d9587512839901A'
//创建一个 web3 示例
    const web3 = new Web3(new Web3.providers.WebsocketProvider('http://localhost:7545'))
    accounts = await web3.eth.getAccounts();
    console.log(accounts);
    loansAbi = require("./build/contracts/Loans.json").abi;
    instance = new web3.eth.Contract(loansAbi, loansAddress);
    // console.log(instance);
    instance.events.ApplicationSubmit({fromBlock:0},function (error, result) {
        if (error) {
            console.error(error);
        } else {
            console.log(result)
        }
    })
})()
  1. Run a demo script,执行合约调用
truffle exec mytoken.js
  1. 获取所有的账户
let accounts = await web3.eth.getAccounts();

8 . 事件及订阅 event :

/**
 * @dev Emitted when `value` tokens are moved from one account (`from`) to
 * another (`to`).
 *
 * Note that `value` may be zero.
 */
event Transfer(address indexed from, address indexed to, uint256 value);

/**
 * @dev Emitted when the allowance of a `spender` for an `owner` is set by
 * a call to {approve}. `value` is the new allowance.
 */
event Approval(address indexed owner, address indexed spender, uint256 value);

抛出事件:

/**
 * @dev Moves tokens `amount` from `sender` to `recipient`.
 *
 * This is internal function is equivalent to {transfer}, and can be used to
 * e.g. implement automatic token fees, slashing mechanisms, etc.
 *
 * Emits a {Transfer} event.
 *
 * Requirements:
 *
 * - `sender` cannot be the zero address.
 * - `recipient` cannot be the zero address.
 * - `sender` must have a balance of at least `amount`.
 */
function _transfer(address sender, address recipient, uint256 amount) internal {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");
    
    _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
    _balances[recipient] = _balances[recipient].add(amount);
    emit Transfer(sender, recipient, amount);
}

订阅:

//监听代币交易事件
    
module.exports = async (callback) => {
    try {
        const MyToken = artifacts.require("MyToken");
        let instance = await MyToken.deployed();
        let BN = require("bn.js");
        let frac = new BN(10).pow(new BN(18));
        instance.Transfer().on('data', function (event) {
            console.log('-----------------');
            console.log('from', event.args.from);
            console.log('to', event.args.to);
            console.log('value', event.args.value.div(frac).toString());
        });
    
    } catch (e) {
        console.log("e = " + e.stack);
        callback(e)
    }
};
//获取所有区块信息
    
module.exports = async (callback) => {
    try {
        const MyToken = artifacts.require("MyToken");
        let instance = await MyToken.deployed();
        let BN = require("bn.js");
        let frac = new BN(10).pow(new BN(18));
        let events = await instance.getPastEvents('allEvents',{
                'fromBlock': 0,
                'toBlock': 'latest'
            },
            function (error) {
                console.log('error', error);
            });
        for (let i = 0; i < events.length; i++) {
            let event = events[i];
            console.log('-----------------------------------');
            console.log(JSON.stringify(event,null,4));
            // console.log('Transfer' + i);
            // console.log('from', event.args.from);
            // console.log('to', event.args.to);
            // console.log('value', event.args.value.div(frac).toString());
        }
        callback();
    } catch (e) {
        console.log("e = " + e.stack);
        callback(e)
    }
};

附录

iban

国际银行账号,简单地说,以太坊中的iban账号是以太坊为了和传统的银行系统对接而引入的概念, web3.js中提供了以太坊地址和iban地址之间的转换方法。

iban这个概念源于传统的银行系统,其英文全称为 International Bank Account Number, 即国际银行帐号。iban的作用是为全球任意一家银行中的任意一个账户生成一个全球唯一的账号,以便进行跨行交易。一个iban账号看起来像这样:

XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS 

iban地址最多可以包含34个字母和数字,其中的字母大小写不敏感。在iban 中包含以下信息:

  • 国别码,用来标识国家,遵循ISO3166-1 alpha-2标准
  • 错误识别码,用来对地址进行校验,采用mod-97-10校验和协议,即ISO/IEC 7064:2003标准
  • 基本银行账号,即BBAN(Basic Bank Account Number),用来标识银行机构、网点及 客户在该机构内的账号,这三部分信息的编码方案依赖于前面提及的国别码

以太坊iban

新的国别码和BBAN编码方案.

以太坊引入了一个新的IBAN国别码:XE,其中E代表Ethereum,X代表非法币(non-jurisdictional currencies)。同时,以太坊提出了三种BBAN的编码格式:direct、basic和indirect。

direct

编码方案中的BBAN为30个字母/数字,只有一个字段:账户编号。例如,以太坊 地址00c5496aee77c1ba1f0854206a26dda82a81d6d8转换为direct方案的BBAN账号,就 得到XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS

let myiban=web3.eth.Iban.fromAddress('0x00c5496aee77c1ba1f0854206a26dda82a81d6d8');
console.log(myiban) //XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS

basic

编码方案与direct方案的唯一区别在于,其BBAN长度为31个字母/数字,因此该方案 不兼容IBAN。

indrect

编码方案中的BBAN长度为16个字母/数字,包含三个字段:

  • 资产编号,由3个字母/数字组成
  • 机构编号,由4个字母/数字组成
  • 机构内客户编号,由9个字母/数字组成

例如,一个采用indrect编码方案的以太坊iban账号,看起来是这样:

XE81ETHXREGGAVOFYORK1

XE81-ETH-XREG-GAVOFYORK1前面的XE表示国别码,81为校验和,后面的16个字符就是indrect编码的BBAN,其中:

  • ETH:在本例中,表示客户账户内的资产编号。目前ETH是唯一有效的资产编号
  • XREG:机构编号,XREG表示以太坊基本注册合约
  • GAVOFYORK:机构内客户的编号

iban账号与以太坊地址的转换

如前所述,使用web3.eth.Iban.fromEthereumAddress()方法,可以将一个以太坊地址 转换为direct编码方案的iban账号。与之对应的,可以使用web3.eth.Iban.toAddress方法, 将一个采用direct编码方案的iban账号,转换回以太坊地址。例如:

let myaddr = web3.eth.Iban.toAddress("XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS")
console.log(myaddr) //0x00c5496aEe77C1bA1f0854206A26DdA82a81D6D812

检查iban账号的有效性

iban账号中的校验和用来帮助核验一个给定字符串是否为有效的iban账号。可以使用 web3.js中的web3.eth.Iban.isValid() 来进行执行校验。例如:

let isValid = web3.eth.Iban.isValid("XE81ETHXREGGAVOFYORK")
console.log(isValid) // true
isValid = web3.eth.Iban.isValid("XE82ETHXREGGAVOFYORK")
console.log(isValid) // false,因为校验和无效

abi

控制台

连接到节点

设置变量

  • abi
abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"int256","name":"age","type":"int256"}],"name":"setAge","outputs":[],"stateMutability":"nonpayable","type":"function","signature":"0xf193dc1d"},{"inputs":[],"name":"getAge","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function","constant":true,"signature":"0x967e6e65"}];
  • address
 address = '0xAa51C85b57b65f46B1b37A95C42C0B4532b317a9';
  • contract
contract = web3.eth.contract(abi);
  • instance
instance = contract.at(address);
  • accounts
accounts = web3.eth.accounts;
This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to <http://unlicense.org>

简介

简单的 truffle 部署智能合约项目 展开 收起
JavaScript 等 2 种语言
Unlicense
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/binny1024/truffle-demo.git
git@gitee.com:binny1024/truffle-demo.git
binny1024
truffle-demo
truffle-demo
main

搜索帮助