孤荷凌寒自学python第123天区块链037以太坊的 erc20代币07

孤荷凌寒自学python第123天区块链037以太坊的 erc20代币07

【主要内容】

今天继续使用erc20标准规范按一篇网络博文的教程进行亲自敲打代码来写一个可以发行代币的智能合约,今天开始在在线remix网站上编译合约,但却失败了。学习共用时46分钟。

(此外整理作笔记花费了约51分钟)

详细学习过程见文末学习过程屏幕录像。

【学习笔记】

一、将代码放到remix在线网站上后,发现了代码中的很多错误,经过修改后的三个sol文件的代码如下:

Token.sol

```

pragma solidity ^0.4.4;

 

//下面定义的是最基本的合约框架,感觉直接照搬了erc20标准接口中的那些 定义

contract Token{

    /// @return 返回此代币要发行的总量的函数方法(只是一个空壳)

    function totalSuply() constant returns (uint256 supply);

    //constant修饰关键词注明此函数是一个常量函数——意味着承诺自己不修改区块链上任何状态

    //constant修饰关键词表示此函数是只读的。

 

    /// @param _owner 要查询的地址

    /// @return 返回对应地址的代币余额,是指eth的余额吗?

    function balanceOf(address _owner) constant returns(uint256 balance);

 

    /// @param _to 调用此合约的节点(msg.sender)要将代币发送给哪个节点地址

    /// @param _value 此次交易要发送多少代币,是指eth吗?

    /// @return 此函数返回的是,此次交易是否完成成功。

    function transferFrom(address _to,uint256 _value) returns(bool success);

 

    /// @notice 此函数用于调用此合约的节点委托授权另一个节点地址(可能是当前合约或另一个合约吗?)可以动用自己多少token

    /// @param _spender 获得调用此合约的节点(msg.sender)授权的一个节点地址。

    /// @param _value 获得了多少token的授权,指的是eth吗?

    /// @return 返回本次委托授权操作是否成功。

    function approve(address _spender,uint256 _value) returns(bool success);

 

    /// @notice 此函数用于查询一次授权的双方(发起委托授权的节点与接受委托授权的节点)当前授权token的可用余额(就是说接受授权节点还可以动用的实际token数量)

    /// @param _owner 发起委托授权的节点地址

    /// @param _spender 授受委托授权的节点地址

    /// @return 返回剩下的可用的授权token数量

    function allowance(address _owner,address _spender) returns(uint256 remaining);

 

    //当出现发送代币的事件时,下面事件被触发。

    event Transfer(address indexed _from,address indexed _to,uint256 _value);

    //在事件的参数中,有两个添加了indexed修饰关键词,参照博文:https://www.jianshu.com/p/131c07c6f72f

    //解释如下 :

    //增加了indexed的参数值会存到日志结构的Topic部分,便于快速查找。而未加indexed的参数值(anonymous 关键词)会存在data部分,成为原始日志。需要注意的是,如果增加indexed属性的是数组类型(包括string和bytes),那么只会在Topic存储对应的数据的web3.sha3哈希值,将不会再存原始数据。因为Topic是用于快速查找的,不能存任意长度的数据,所以通过Topic实际存的是数组这种非固定长度数据哈希结果。要查找时,是将要查找内容哈希后与Topic内容进行匹配,但我们不能反推哈希结果,从而得不到原始值。

    //最多只能给事件的三个参数添加Indexed修饰词。

 

    //当出现一个节点授权另一个节点地址(可能是合约所在的节点地址?)进行指定数量的代币处置权时,下面的事件被触发

    event Approval(address indexed _owner,address indexed _spender,uint256 _value);

    //_owner表示发起授权请求的节点地址;_spender表示接受授权请求的节点地址;_value表示授权可以处置的代币的最大数量。

}

 

```

StandardToken.sol

```

pragma solidity ^0.4.4;

 

import "Token.sol";

 

contract StandardToken is Token{

    mapping (address => uint256) balances;

    mapping (address => mapping (address => uint256)) allowed;

    uint256 public totalSupply;

    function transfer(address _to,uint256 _value) returns (bool success){

        //此函数仅用于调用合约的节点(或者理解为创建这个合约的节点?)地址进行代币的发送到_to形参指定的节点地址。

        //可以发送的代币的数量_value这个形参的数值类型是 uint256,可以理解为只能是大于等于零的数,且最大值为2^256-1(为什么要减一呢?因为从0开始)

        //实际上这个形参的数值类型就限定了最大可以发送的代币的数量,最多只允许2^256-1。

        //下一行代码要进行发送方现有代币余额的检查,以确认可以发送这么多的代币。

        if (balances[msg.sender]>=_value && _value>0){

            //--如果发送方节点有这么多_value,那么执行代币在两个节点间的转移

            balances[msg.sender]-=_value;

            balances[_to]+=_value;

            //---触发事件----

            Transfer(msg.sender,_to,_value);

            //--返回处理结果----

            return true;

        }else{

            return false;

        }

 

    }

 

    function transferFrom(address _from,address _to,uint256 _value) returns (bool success){

        //此函数形参_from指定的节点地址进行代币的发送到_to形参指定的节点地址。(是否是在进行授权节点(合约的节点?)的代币支配转移?)

        //可以发送的代币的数量_value这个形参的数值类型是 uint256,可以理解为只能是大于等于零的数,且最大值为2^256-1(为什么要减一呢?因为从0开始)

        //实际上这个形参的数值类型就限定了最大可以发送的代币的数量,最多只允许2^256-1。

        //下一行代码要进行发送方现有代币余额的检查,以确认可以发送这么多的代币。

        //同时检查,发送方授权给授权接受方的可支配代币金额是否大于等于_value

        if (balances[_from]>=_value && allowed[_from][msg.sender]>=_value && _value>0){

            //allowed[_from][msg.sender]中:

            //_from指的是代币真正的发送方(此节点代币才真正减少),而[msg.sender]指的是_from授权的接受节点(此节点可以动用_from节点限定数量的_value)

            //为什么这儿的授权接受方是[msg.sender]没有完全理解 。

            //--如果发送方节点有这么多_value,那么执行代币在两个节点间的转移

            balances[_from]-=_value; //真正发送代币的节点的代币数量减少

            balances[_to]+=_value; //接收代币的节点代币数量增加

            allowed[_from][msg.sender]-=_value; //授权的可动用的代币数量也减少

            //上一行与后面的approve()函数中的allowed的对象多重列表的表示感觉有点混乱,不好理解,其实原因是:

            //此函数一般由之前接受过某节点(这儿指_from节点)授权的节点来调用,因此此处allowed对象表示接受授权的节点就是【msg.sender】(指当前调用合约的这个节点)

 

            //---触发事件----

            Transfer(_from,_to,_value);

            //--返回处理结果----

            return true;

        }else{

            return false;

        }

 

    }

 

    function balanceOf(address _owner) constant returns (uint256 balance){

        //此合约函数用于外部调用合约时,返回指定节点_owner目前的代币余额。

        return balance[_owner];

    }

 

    function approve(address _spender, uint256 _value) returns (bool success) {

        //此合约由调用合约的节点(msg.sender)向指定的节点_spender授权,让_spender节点可以调用发起授权的节点指定_value数量的值。

        //调用此合约的这个函数方法的节点,将通过此函数方法发起授权,因此 这儿——调用此合约的节点:msg.sender就是发起授权的一方。

        allowed[msg.sender][_spender]=_value;

        //下面显式引发事件

        Approve(msg.sender,_spender,_value);

        return true;

    }

 

    function allowance(address _owner,address _spender) returns (uint256 remaining){

        //此函数供查询指定的一个授权节点_owner与接受授权的节点_spender之间授权内容中,目前还剩余的授权_value是多少。

        return allowed[_owner][_sender];

    }

 

}

 

```

ghlhToken.sol

```

pragma solidity ^0.4.4;

 

import "StandardToken.sol";

 

contract ghlhToken is StandardToken {

 

    //下一个函数没有函数命名名称,属于特殊的函数

    //此函数的基本作用是,如果收到了没有指明任何相关信息的代币发送给此合约,就将这些代币扔回去。

    //此种函数名叫:Fallback 函数

    function (){

        throw; //这个单词的意思就是【扔】

    }

 

    //定义与自发行的币相关的一些属性

    string public name;                   //token名称: ghlhToken

    uint8 public decimals;                //小数位,此币种最小的单位,允许到小数点后第几位

    string public symbol;                 //标识,就是代币的缩写,如BTC

    string public version = 'H0.1';       //版本号

    //现在定义合约的建构函数

    function ghlhToken(

        uint256 _initialAmount,

        string _tokenName,

        uint8 _decimalUnits,

        string _tokenSymbol

    ){

        balances(msg.sender)=_initialAmount; //这意味着,部署合约的节点首先获得全部要发行的币的总量

        //balances这个状态变量,是在此合约继承的父合约中定义的。

        totalSupply=_initialAmount; //此状态变量记录下总共要发行的币的总量。

        name = _tokenName;                                   // token名称

        decimals = _decimalUnits;                            // token小数位

        symbol = _tokenSymbol;                               // token标识

    }

 

    //下面这个函数不属于erc20标准接口,同时完成授权与将要发送给服务合约的数据发送出去。

    //https://mp.weixin.qq.com/s/foM1QWvsqGTdHxHTmjczsw 此博文对此有比较详细的解释,不过仍然没有完全理解

    function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {

        allowed[msg.sender][_spender] = _value;

        Approval(msg.sender, _spender, _value); //引发事件,广播一次授权事件正在发生。

 

        //调用你想要通知的服务合约的 receiveApprovalcall 方法(是指提供服务的服务合约中的方法,不是当前(当前是一个代币合约)合约中的函数) ,在服务合约(就是接收实际要处理的,由发送方(此处批授权发起节点)发送的数据的合约方)中此函数的定义内容大致如下:

        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)

 

        //下一语句,先检查调用服务合约的receiveApprovalcall()方法是否成功,如果不成功,就throw;这个时候,是否意味着还是应当调用标准的approve()方法?

        if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }

       

        //---首先来自中文版官方文档的说明:https://solidity-cn.readthedocs.io/zh/develop/units-and-global-variables.html?highlight=%20keccak256#id3

        //sha3---

        //sha3(...) returns (bytes32):

        //等价于 keccak256。

        //keccak256---

        //keccak256(...) returns (bytes32):

        //计算 (tightly packed) arguments 的 Ethereum-SHA-3 (Keccak-256)哈希。

        //我的理解就是,将后面参数直接求出其哈希值来

        //博文:https://www.jianshu.com/p/682c75b10392 的说明如下:

        //SHA3采用Keccak算法,在很多场合下Keccak和SHA3是同义词,但在2015年8月SHA3最终完成标准化时,NIST调整了填充算法,标准的SHA3和原先的Keccak算法就有所区别了。在早期的Ethereum相关代码中,普遍使用SHA3代指Keccak256,为了避免和NIST标准的SHA3混淆,现在的代码直接使用Keccak256作为函数名。总结为一句话:Ethereum和Solidity智能合约代码中的SHA3是指Keccak256,而不是标准的NIST-SHA3,为了避免混淆,直接在合约代码中写成Keccak256是最清晰的。

        //--------------------------------------

        //bytes32---

        //没有找到bytes32作为函数的相关说明,我的初步理解是,就参数转换为bytes32类型的数据

        //bytes4---

        //我认为理解起来相当于强制转换的使用。

        //_spender.call----

        //http://www.chidaolian.com/article-6311-1

        //节点.call(要调用的目标(即节点)合约中的函数方法选择器,此选择的函数需要的多个参数)

        //根据此博文的描述,使用call调用是高风险的,因此 发现其它博文中的示例代码没有使用这种写法。

        //如:https://mp.weixin.qq.com/s/foM1QWvsqGTdHxHTmjczsw

        //中是这样来书写的:

        //TokenRecipient(_recipient).receiveApproval(msg.sender,

        //                                     _value,

        //                                     address(this),

        //                                     _extraData);

        //此处的_recipient应当指的就是授受授权与提供服务的服务合约的地址。

        //而TokenRecipient是使用interface方式定义的一个抽象合约(只能作为基类合约供其它合约继承,抽象合约中的函数定义是空的),不过Interface定义的这种抽象合约与之前 直接用contract定义的似乎不同。

        //用interface定义的这个抽象合约,就是专门用以跨合约之间进行函数方法的相互调用与交互的,

        //见博文:https://blog.csdn.net/weixin_34291004/article/details/91902209

        //我尚且没有完全理解。不过这种方法是否就比调用call的方法要更安全呢?

        //那这儿的这句代码也就相当于调用了服务合约的receiveApproval()方法,用以同时完成接受授权并接收收到的data数据并处理数据,完成提供的本次服务。

 

        //无论结果如何,本函数都返回成功标志,这合理?

        return true;

    }

 

}

 

```

 

二、remix在线编译遇到的困难

(一)提示

Compiler not yet loaded.

最开始的时候,一是英文不好,根本无法理解,后来百度翻译了——编译器没有加载——还是一头雾水,完全不知所措,搜索网络也一无所获。

最后才缓过神来,原来是因为网速缓慢,选择的编译器版本还没有加载成功 ,果然过一段时间后,就不再有这样的提示了。

(二)提示

you are using an `https` connection. Please switch to `http` if you are using Remix against an `http Web3 provider` or allow Mixed Content in your browser.

原来remix的在线编译器,既可以使用http://头开始,也可以使用https://头开始

(三)发现了还在使用旧版本的remix在线编译器的中文版本

(四)智能合约多次继承,产生多个文件时,会提示;

URL not parseable: StandardToken.sol

中文翻译:

URL不可解析:standardtoken.sol

没有找到解决方案——

最后,只好将所有合约的继承关系删除,将合约合并成一个单独的合约:

最后的,ghlhToken.sol文件代码如下:

```

pragma solidity ^0.4.4;

 

contract ghlhToken {

    mapping (address => uint256) balances;

    mapping (address => mapping (address => uint256)) allowed;

    uint256 public totalSupply;

   

 

    //定义与自发行的币相关的一些属性

    string public name;                   //token名称: ghlhToken

    uint8 public decimals;                //小数位,此币种最小的单位,允许到小数点后第几位

    string public symbol;                 //标识,就是代币的缩写,如BTC

    string public version = 'H0.1';       //版本号

   

   

    //当出现发送代币的事件时,下面事件被触发。

    event Transfer(address indexed _from,address indexed _to,uint256 _value);

    //在事件的参数中,有两个添加了indexed修饰关键词,参照博文:https://www.jianshu.com/p/131c07c6f72f

    //解释如下 :

    //增加了indexed的参数值会存到日志结构的Topic部分,便于快速查找。而未加indexed的参数值(anonymous 关键词)会存在data部分,成为原始日志。需要注意的是,如果增加indexed属性的是数组类型(包括string和bytes),那么只会在Topic存储对应的数据的web3.sha3哈希值,将不会再存原始数据。因为Topic是用于快速查找的,不能存任意长度的数据,所以通过Topic实际存的是数组这种非固定长度数据哈希结果。要查找时,是将要查找内容哈希后与Topic内容进行匹配,但我们不能反推哈希结果,从而得不到原始值。

    //最多只能给事件的三个参数添加Indexed修饰词。

 

    //当出现一个节点授权另一个节点地址(可能是合约所在的节点地址?)进行指定数量的代币处置权时,下面的事件被触发

    event Approval(address indexed _owner,address indexed _spender,uint256 _value);

    //_owner表示发起授权请求的节点地址;_spender表示接受授权请求的节点地址;_value表示授权可以处置的代币的最大数量。   

   

    //下一个函数没有函数命名名称,属于特殊的函数

    //此函数的基本作用是,如果收到了没有指明任何相关信息的代币发送给此合约,就将这些代币扔回去。

    //此种函数名叫:Fallback 函数

    function (){

        throw; //这个单词的意思就是【扔】

    }

       

   

    //现在定义合约的建构函数

    function ghlhToken(

        uint256 _initialAmount,

        string _tokenName,

        uint8 _decimalUnits,

        string _tokenSymbol

    ){

        balances(msg.sender)=_initialAmount; //这意味着,部署合约的节点首先获得全部要发行的币的总量

        //balances这个状态变量,是在此合约继承的父合约中定义的。

        totalSupply=_initialAmount; //此状态变量记录下总共要发行的币的总量。

        name = _tokenName;                                   // token名称

        decimals = _decimalUnits;                            // token小数位

        symbol = _tokenSymbol;                               // token标识

    }

 

    function totalSuply() constant returns (uint256 supply){

        return totalSupply;

    }

    //constant修饰关键词注明此函数是一个常量函数——意味着承诺自己不修改区块链上任何状态

    //constant修饰关键词表示此函数是只读的。

 

    function transfer(address _to,uint256 _value) returns (bool success){

        //此函数仅用于调用合约的节点(或者理解为创建这个合约的节点?)地址进行代币的发送到_to形参指定的节点地址。

        //可以发送的代币的数量_value这个形参的数值类型是 uint256,可以理解为只能是大于等于零的数,且最大值为2^256-1(为什么要减一呢?因为从0开始)

        //实际上这个形参的数值类型就限定了最大可以发送的代币的数量,最多只允许2^256-1。

        //下一行代码要进行发送方现有代币余额的检查,以确认可以发送这么多的代币。

        if (balances[msg.sender]>=_value && _value>0){

            //--如果发送方节点有这么多_value,那么执行代币在两个节点间的转移

            balances[msg.sender]-=_value;

            balances[_to]+=_value;

            //---触发事件----

            Transfer(msg.sender,_to,_value);

            //--返回处理结果----

            return true;

        }else{

            return false;

        }

 

    }

 

    function transferFrom(address _from,address _to,uint256 _value) returns (bool success){

        //此函数形参_from指定的节点地址进行代币的发送到_to形参指定的节点地址。(是否是在进行授权节点(合约的节点?)的代币支配转移?)

        //可以发送的代币的数量_value这个形参的数值类型是 uint256,可以理解为只能是大于等于零的数,且最大值为2^256-1(为什么要减一呢?因为从0开始)

        //实际上这个形参的数值类型就限定了最大可以发送的代币的数量,最多只允许2^256-1。

        //下一行代码要进行发送方现有代币余额的检查,以确认可以发送这么多的代币。

        //同时检查,发送方授权给授权接受方的可支配代币金额是否大于等于_value

        if (balances[_from]>=_value && allowed[_from][msg.sender]>=_value && _value>0){

            //allowed[_from][msg.sender]中:

            //_from指的是代币真正的发送方(此节点代币才真正减少),而[msg.sender]指的是_from授权的接受节点(此节点可以动用_from节点限定数量的_value)

            //为什么这儿的授权接受方是[msg.sender]没有完全理解 。

            //--如果发送方节点有这么多_value,那么执行代币在两个节点间的转移

            balances[_from]-=_value; //真正发送代币的节点的代币数量减少

            balances[_to]+=_value; //接收代币的节点代币数量增加

            allowed[_from][msg.sender]-=_value; //授权的可动用的代币数量也减少

            //上一行与后面的approve()函数中的allowed的对象多重列表的表示感觉有点混乱,不好理解,其实原因是:

            //此函数一般由之前接受过某节点(这儿指_from节点)授权的节点来调用,因此此处allowed对象表示接受授权的节点就是【msg.sender】(指当前调用合约的这个节点)

 

            //---触发事件----

            Transfer(_from,_to,_value);

            //--返回处理结果----

            return true;

        }else{

            return false;

        }

 

    }

 

    function balanceOf(address _owner) constant returns (uint256 balance){

        //此合约函数用于外部调用合约时,返回指定节点_owner目前的代币余额。

        return balance[_owner];

    }

 

    function approve(address _spender, uint256 _value) returns (bool success) {

        //此合约由调用合约的节点(msg.sender)向指定的节点_spender授权,让_spender节点可以调用发起授权的节点指定_value数量的值。

        //调用此合约的这个函数方法的节点,将通过此函数方法发起授权,因此 这儿——调用此合约的节点:msg.sender就是发起授权的一方。

        allowed[msg.sender][_spender]=_value;

        //下面显式引发事件

        Approve(msg.sender,_spender,_value);

        return true;

    }

 

    function allowance(address _owner,address _spender) returns (uint256 remaining){

        //此函数供查询指定的一个授权节点_owner与接受授权的节点_spender之间授权内容中,目前还剩余的授权_value是多少。

        uint256 v=allowed[_owner][_sender];

        return v;

    }

 

    //下面这个函数不属于erc20标准接口,同时完成授权与将要发送给服务合约的数据发送出去。

    //https://mp.weixin.qq.com/s/foM1QWvsqGTdHxHTmjczsw 此博文对此有比较详细的解释,不过仍然没有完全理解

    function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {

        allowed[msg.sender][_spender] = _value;

        Approval(msg.sender, _spender, _value); //引发事件,广播一次授权事件正在发生。

 

        //调用你想要通知的服务合约的 receiveApprovalcall 方法(是指提供服务的服务合约中的方法,不是当前(当前是一个代币合约)合约中的函数) ,在服务合约(就是接收实际要处理的,由发送方(此处批授权发起节点)发送的数据的合约方)中此函数的定义内容大致如下:

        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)

 

        //下一语句,先检查调用服务合约的receiveApprovalcall()方法是否成功,如果不成功,就throw;这个时候,是否意味着还是应当调用标准的approve()方法?

        if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }

       

        //---首先来自中文版官方文档的说明:https://solidity-cn.readthedocs.io/zh/develop/units-and-global-variables.html?highlight=%20keccak256#id3

        //sha3---

        //sha3(...) returns (bytes32):

        //等价于 keccak256。

        //keccak256---

        //keccak256(...) returns (bytes32):

        //计算 (tightly packed) arguments 的 Ethereum-SHA-3 (Keccak-256)哈希。

        //我的理解就是,将后面参数直接求出其哈希值来

        //博文:https://www.jianshu.com/p/682c75b10392 的说明如下:

        //SHA3采用Keccak算法,在很多场合下Keccak和SHA3是同义词,但在2015年8月SHA3最终完成标准化时,NIST调整了填充算法,标准的SHA3和原先的Keccak算法就有所区别了。在早期的Ethereum相关代码中,普遍使用SHA3代指Keccak256,为了避免和NIST标准的SHA3混淆,现在的代码直接使用Keccak256作为函数名。总结为一句话:Ethereum和Solidity智能合约代码中的SHA3是指Keccak256,而不是标准的NIST-SHA3,为了避免混淆,直接在合约代码中写成Keccak256是最清晰的。

        //--------------------------------------

        //bytes32---

        //没有找到bytes32作为函数的相关说明,我的初步理解是,就参数转换为bytes32类型的数据

        //bytes4---

        //我认为理解起来相当于强制转换的使用。

        //_spender.call----

        //http://www.chidaolian.com/article-6311-1

        //节点.call(要调用的目标(即节点)合约中的函数方法选择器,此选择的函数需要的多个参数)

        //根据此博文的描述,使用call调用是高风险的,因此 发现其它博文中的示例代码没有使用这种写法。

        //如:https://mp.weixin.qq.com/s/foM1QWvsqGTdHxHTmjczsw

        //中是这样来书写的:

        //TokenRecipient(_recipient).receiveApproval(msg.sender,

        //                                     _value,

        //                                     address(this),

        //                                     _extraData);

        //此处的_recipient应当指的就是授受授权与提供服务的服务合约的地址。

        //而TokenRecipient是使用interface方式定义的一个抽象合约(只能作为基类合约供其它合约继承,抽象合约中的函数定义是空的),不过Interface定义的这种抽象合约与之前 直接用contract定义的似乎不同。

        //用interface定义的这个抽象合约,就是专门用以跨合约之间进行函数方法的相互调用与交互的,

        //见博文:https://blog.csdn.net/weixin_34291004/article/details/91902209

        //我尚且没有完全理解。不过这种方法是否就比调用call的方法要更安全呢?

        //那这儿的这句代码也就相当于调用了服务合约的receiveApproval()方法,用以同时完成接受授权并接收收到的data数据并处理数据,完成提供的本次服务。

 

        //无论结果如何,本函数都返回成功标志,这合理?

        return true;

    }

 

```

不过仍然没有编译通过,提示以下两处错误 ,原因都 是没有 声明这个变量:

browser/ghlhToken.sol:114:9: DeclarationError: Undeclared identifier.

Approve(msg.sender,_spender,_value);

^-----^

browser/ghlhToken.sol:120:35: DeclarationError: Undeclared identifier.

uint256 v=allowed[_owner][_sender];

^-----^

然而事实上:

事件Approve是声明 了的。

而allowed状态变量也是声明了的。

今天学习到 这儿就完全没有进展了,恳请高手指导,万分感激,只能后续再继续学习。

【今天的自学感悟分享】

困难总是在前进的道路上不期而遇,然而,从出生那一刻起,每个人就是来迎接这些困难的挑战的,可以说,这一世不过就是一个闯关的过程,如果玩过游戏就会明白,无数次的闯关才能换回一点点的收获,这就是这个世界的规则。既然游戏规则如此,就不能说我要放弃,因为其实我们连放弃的机会都没有。

在这个竞争到白热化,忙碌到没时间的时代,焦虑与急功近利成了不知多少人的标准心态。

于是乎,减肥需要一个月到位的速成,健身需要20天的塑形速成……

速溶咖啡,速泡方便面是这个时代的缩影。

那么学习可以速成吗?

前不久,有位朋友突然想学编程了,他说他请外包始终弄不好,精力时间花了不少,因为自己一点不懂,交流上有隔阂是始终没弄好的原因,于是他想自己要是有一些编程基础,可能确实有助于更好服务他的生意。便他最后却放弃了,因为——

我告诉他,好啊,要基本理解编程相关的概念和思维,三个月足够了,要能够自己写出点东西来,一年内应当没有问题……

我话没能说完,他急了:“一天能教会我不?你知道我很忙的,我也知道你是高手,不,是专家,对你来说,一天教会我没问题的。”

我愣在那儿完全哑口无言。

这就是人们的想法,一个速成的时代的特征。可是学习这回事,还真不是老师可以帮上忙的,学习本来就是自己的事,而且学习的目的其实就是为了不用人教,不用人教,那就是要学会自学能力啊!可自学到底是什么呢?

更多的我关于自学的感悟,我将与大家在【就是要学】社群交流互动。

我建立【就是要学】社群的初衷就是将渴望与我一样追求自主独立的生活的朋友 ,特别是还有大把青春可以去试错的年轻朋友聚集到一起,在这个人以类聚的时代,我们在一起, 互相交流,坚持每天成长,欢迎来到【就是要学】社群QQ群:646854445

或访问:www.941xue.com

【关于坚持自学的例行说明】

最后例行说明下,我为什么要坚持自学。

“如果我不曾见过太阳,我本可以忍受黑暗,然而阳光已使我的荒凉,成为更新的荒凉。”

——艾米莉·狄金森

如果要问我对自己的前半生如何看待时,我想昨天和今天的答案都将完全不同。

昨天的我,生活在荒凉的满意之中,自觉怡然自得,拿着包身包月的工资,听着仁慈的命令,过着几乎一成不变的生活;时而与周遭的人儿和睦互往,时而唇舌相抵斤斤计较,演出着生活的鸡毛蒜皮,工作的吹拉弹唱;忘我,忘我,才能融入这平和无奇的乐章中,迈着细碎的步伐,原地踏步。那时的我觉得这就是悠然自得的听天由命的平凡人生,也就是我的宿命了。

可是某一天,我见到了不一样的太阳以及太阳下不一样的人生光景——那并不荒凉。

今天的我,生活在荒凉的痛苦之中,自觉渴望改变,迈着不知所措的步伐,看着流逝的年华,睁着悔恨错失一切的双眼… …

我知道我将再无法回到过去的我,只有改变才是唯一正确的方向。

一、为什么一把年纪还在学习

放弃很多去聚餐,去HI歌,去游玩,去看电影,去追剧……的时间,然后进行着这个年纪似乎已不应当再进行的学习,引来身边人们无尽的不解与鄙夷甚至可怜……

但我不想放弃终身学习的誓言。

因为——

我对我今天的生活现状并不认同!

罗伯特清崎告诉过我们,反省自己当下的生活是不是自己想要的,这难道不是最好的动力与答案?

走过了大半生,然后才发现曾经、当下所正在进行的人生并不是自己想要的,那是一种怎样的体验?

只有心中真切的感受才能回答这个问题,而任凭再丰富的语言也是无法描绘出来的。

经历半生的跋涉,却发现走得并不正确,有多少人有勇气承认自己过去的一切都是错误的呢?

而我愿意告诉过去的我:“你错了!”

那么已经历半生错误,年岁之大又压于头顶,还有希望从这架的*的半端重新爬下,再蹒跚着爬上另一架*吗?

我宁愿相信还有希望!

这便是我为什么要继续坚持终身学习下去的全部理由。

二、这个年纪还在学这些技术有意义吗

纯的技术对这把年纪其实已没有意义。

但兴趣可以超越意义。

但技术可以引来思想的变革,这才是意义。

投资自己的头脑 ,改革自己的思想,这是最保值,更长远的投资,过去我从来没有投资过,错过太多,那就从投资自己头脑开始吧。

罗伯特清崎告诉我们,真正的富有是时间的富有;真正的*是可以决定自己愿意做什么的*。

因为我愿意做我兴趣所在的事,所以我希望我有*选择的那一天,虽然今天离那一天可能还是那么遥远,但我愿意相信,每天多赶几步,离希望就更近一步。

再者,虽然我可能再已无法完全完整的掌握这些技术了,但技术本身却可以启迪心的觉醒,激发灵感,那么只要多了解一点,我相信我将离那个正离我而去跑得越来越快的未来更近一点,不至于被未知的那个未来抛弃得太远。

于是我怎能放弃追逐求索的步伐?

我要坚信:感觉太迟的时候,也许还不算太迟。

感谢一直以来关注我,鼓励我的你!

【同步语音笔记】

https://www.ximalaya.com/keji/19103006/271221588

【学习过程屏幕录屏】

https://www.bilibili.com/video/av96842974/