Solidity (快速入门)

Solidity——Hello world

在线Remix编辑器: Remix - Ethereum IDE

//声明版本号
pragma solidity ^0.6.0;
//合约 有点类似于java中的class
contract helloworld {
    //合约属性变量
    string hello = "hello world";
    function getName() public view  returns(string memory)
    {
        return hello;
    }
    //可以修改属性变量的值 消耗gas
    function changeName(string memory _newName) public{
        hello = _newName;
    }
​
    function pureName(string memory _name) public pure returns(string memory){
        return _name;
    }
}

注意的是:程序中的版本号要和右侧编译器版本号一致

小知识:

用constant、view、pure修饰function分别表示

  • constant:只能读取不可改变状态变量(就是contract中定义的变量)

  • view:只能读取不可改变状态变量,和constant一样

  • pure:不能读取也不能改变状态变量

编写合约->编译->部署

  1. 交易状态:代表我们交易的执行结果。

  2. 交易哈希:标记转账需要的字段,通俗来讲就是个人转账凭证。每一个哈希对应交易是唯一的,可

以确认交易的隐私性和安全性。

  1. 合约地址:我们合约部署的区块的地址。

  2. from:发送者地址

  3. to:接收者地址。

  4. gas:交易环境能耗上限。

  5. 交易消耗:该笔交易占用gas。

  6. 执行消耗:该笔交易执行消耗的gas

  7. 哈希值:应该是破解工作量证明的hash值吧???但是和上面的交易hash区别在哪里呢?

  8. input、decoded input decoded output 这几个东东目前还不能理解是啥。先搁着。

数据存储篇

数组

固定长度字节数组

byte 代表 bytes1

pragma solidity ^0.6.0;
contract ByteArray{
    byte public name1 = 0x12;
    bytes1 public num1 = 0x7a;  //0111   1010
    bytes2 public num2 = 0x7a68;
    bytes12 public num3 = 0x7a68656e676a69616e78756e;
}

动态长度字节数组

pragma solidity ^0.6.0;
contract DynamicByteArray{
    bytes public name = new bytes(2);
    function initName() public{
        name[0] = 0x7a;
        name[1] = 0x68;
    }
    function getLength() public  view returns(uint){
        return name.length;
    }
    function changeName() public{
        name[0] = 0x88;
    }
    
}

动态数组,还提供了一个 push 方法,可以在我们自己数组的末尾继续添加我们的字节元素

function pushTest() public{
    name.push(0x99);
}

字符串

现string并没有给我们提供长度属性,通过 bytes() 进行了强制转换后获取

pragma solidity ^0.6.0;
contract DynamicString{
    string public  name = "tongxuejava";
    function getLength() public view  returns(uint){
        return bytes(name).length;
    }
    function getPartName() public view  returns(bytes1){
        return bytes(name)[0];
    }
}

固定长度字节数组转化

pragma solidity ^0.6.0;
​
contract DynamicString{
    bytes12 name = 0x7a68656e676a69616e78756e;
    function changeBytes1() public  view returns(bytes1){
        return bytes1(name);
    }
    function changeByte2()  public view returns(bytes2){
        return bytes2(name);
    }
    function changeByte3()  public view returns(bytes16){
        return bytes16(name);
    }
}

        转小从头截取,转大末尾补零

固定长度字节数组转动态字节数组

pragma solidity ^0.6.0;
​
contract DynamicString{
    bytes12  name = 0x7a68656e676a69616e78756e;
    function fixBytesToDynamicBytes() public  view returns(bytes memory){
        //return bytes(name);
        bytes memory newName = new bytes(name.length);
        for(uint i= 0;i < name.length;i++){
            newName[i] = name[i];
        }
        return newName;
    }
}

上述注释部分,我们直接return想直接转成动态字节数组,显然是行不通的。于是,我们采用迂回战术,使用一个for循环来挨个字节进行转换,目的达成。

然后这里,我们要注意两点:

一是局部变量(也就是newName在我们的方法内部,这里需要加memory。)

二是,for循环的初始化参数,我们使用uint:代表无符号整型。这里有别与其他语言哦。

动态长度字节数组转为string

string()

pragma solidity ^0.6.0;
​
​
contract Bytes2String{
bytes name = new bytes(2);
    function init() public {
        name[0] = 0x7a;
        name[1] = 0x68;
    }
    function bytesToString() public view returns(string memory){
        return string(name);
    }
}

固定长度数组转string

pragma solidity ^0.6.0;

contract Bytes32ToString{
    bytes2 name = 0x7a68;
    function bytes32ChangeString()public view  returns(string memory){
        bytes memory array  = new bytes(name.length);
        for(uint i = 0;i<array.length;i++){
            array[i]=name[i];
        }
        return string(array);
    }
}

如果输入 0x7a68 ,输出 zh ,但是我们会发现后续还跟了很多个零,具体代码如下:

pragma solidity ^0.6.0;

contract Bytes32ToString{
    bytes2 name = 0x7a68;
    function bytes32ChangeString()public view  returns(string memory){
        bytes memory array  = new bytes(name.length);
        uint count = 0;
        for(uint i = 0;i<name.length;i++){
            if(name[i]!=0){
                count++;
            }
        }
          for(uint i = 0;i<count;i++){
            array[i]=name[i];
        }

        return string(array);
    }
}

字节数组小节

到这里,字节数组就告一段落啦,我们在此做一个小节.

  • 固定长度字节数组

    • byte1 到 byte32

  • 动态长度字节数组

    • bytes的初始化--new bytes

    • 获取bytes的长度和内容

    • 修改长度和内容

  • string

    • 不能够直接获取长度和内容

    • 需要转换为bytes获取长度和内容

    • 特殊字符的长度的内容和获取

    • 中文字符占用3个字节

  • 固定长度字节数组之间转换

    • 转小从头截取,转大末尾补零

  • 固定长度转动态长度字节数组

    • 利用new bytes(),然后循环转换。

  • 动态长度字节数组转string

    • 强制转换string();

  • 固定长度字节数组转string

    • 先获取输入字节数组长度,然后再转动态长度字节数组,在强制转换为string();

固定数组

数组主要就那么几个知识点

  • 默认值与初始化赋值

  • 获取数组长度和数组内容

  • 遍历数组内容

  • 改变数组长度和数组内容

首先我们来看看固定数组的默认值,都为0

对于固定数组,不能修改它的长度或者是添加元素 push()也不行

可变长度数组

至于初始化、获取数组长度和内容,和我们的固定数组是一样的,而对于可变数组,能修改它的长度或者是添加元素 push()也行

固定二维数组

pragma solidity ^0.6.0;
contract TwoArray{
	uint[2][3] arr = [[1,2],[23,4],[6,7]];
    function getLength() view returns(uint){
   		return arr.length;
    }
}

上图的二维数组中有3个元素 每个元素中有两个int型;和我们其他编程语言类似,但稍稍会有点差别。

数组的下标都是从0开始的

可变长度二维数组

pragma solidity ^0.6.0;
contract TwoArray{
	uint[][] arr = [[1,2],[23,4],[6,7]];
    function getLength()public  view returns(uint){
   		return arr.length;
    }
}

差异1:可变长度二维数组不支持直接获取数组内容

差异2:可变长度二维数组支持修改其长度——push()

数组字面量

在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法.

先来看一个例子:

pragma solidity ^0.6.0;
contract ArrayLiterals{
    function getArrayLiterals() public returns(uint[3] memory){
        return [1,2,3];
    }
    function getArrayLiterals2() public returns(uint[3] memory){
        return [256,2,3];
    }
}

TypeError : Return argument type uint8[3] memory is not implicitly convertible to expected type (type of first return variable) uint256[3] memory.

return [1,2,3];

我们查看错误信息会发现,第一个我们返回的数组元素 1,2,3 ,都是 uint8类型的 ,因为1,2,3 都只占用1个字节,也就是8位。而uint[3]相当于uint[256]3,因为uint默认是256位的。第二个我们返回的数组元素 256,2,3 ,其中256是16位(因为我们知道255是8位的最大十进制数,所以256占两个字节,16位)而剩下的都是 uint8类型的 ,而uint[3]相当于uint[256]3,因为uint默认是256位的。

解决方案一:将返回值参数中的类型改为数组元素中最大值的类型即可。

pragma solidity ^0.4.16;
contract ArrayLiterals{
    function getArrayLiterals2() view returns(uint16[3]){
    	return [256,2,3];
    }
}

解决方案二:以通过这种强转的方式

pragma solidity ^0.4.16;
contract ArrayLiterals{
	function getArrayLiteterrals5() view returns(uint[3]){
		return [uint(1),2,3];
	}
}

数组字面量有什么用呢?因为在实际的应用当中,我们的合约大多数形式就是通过数组来传参的,例如我们要对一个数据进行就和

 function getArrrayLiteralalss6(uint[3] memory arr)public pure returns(uint){
        uint sum = 0;
        for(uint i= 0;i<arr.length;i++){
            sum += arr[i];
        }
        return sum;
    }

函数修饰符

view

view :表示一个函数不能修改状态

以下几种情况认为是修改了状态

  1. 写状态变量

  2. 触发事件

  3. 创建其他的合约

  4. call调用附加了以太币

  5. 调用了任何没有view或者pure修饰的函数

  6. 使用了低级别的调用(low-level calls)

pragma solidity >=0.4.0;
 
 
contract User {
   
    // 状态变量
    uint public user_age = 12;
    
    // view修饰的条件(只读取状态,但不修改状态)
    // 本地运行,不消耗gas
    function get_age() public view returns(uint){
        return user_age;
    }
    
    
 
    // pure修饰的条件(不读取且不修改任何状态)
    // 本地运行,不消耗gas
    function get_pure() public pure returns(string memory){
        return "hello";
    }
}

pure

pure:表示一个函数不读取状态,也不修改状态

以下几种情况认为是读取了状态:

  1. 读状态变量

  2. 访问了.balance属性

  3. 访问了block、tx、msg 成员(msg.sig和msg.data除外)

  4. 调用了任何没有pure修饰的函数

地址交易篇

以太坊地址的本质

在以太坊中,有两种类型的账户:一种是外部账户(EOAs,Externally Owned Accounts),另一种是合约账户(Contracts Accounts)。当我们提到账户这个术语的时候,我们通常指的是外部账户(EOA),当提到合约账户的时候我们通常称其为“合约”。

而以太坊的地址分为两种,普通地址和合约地址

  1. 普通地址:普通账号地址,可以进行转账交易,可以显示余额,可以发送交易,通过私钥控制,没有相关联的代码。就像是银行账户,每个用户都有属于自己的账户和密码,只能互相转账,没有其他属性。

  2. 合约地址:合约本身包含了一段代码,当满足合约的特定条件时便会运行条约代码,不单用于转账。就像A与B签订了一个合同,当A满足了合同上的条件,B才会做执行一些任务。而A需要满足的条件和B会执行的任务都是在创建合约地址时就被记录在合约地址代码当中的。

如下图所示,标识1的地方就是我们的外部账户地址,而标识2就是我们的合约地址:

pragma solidity ^0.6.0;
contract AddressTest{
address public account;
//0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
    address public account1 = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
    address public account2 = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
    function changeIt()public view returns(uint160){
        return uint160(account1);
        //1154414090619811796818182302139415280051214250812
    }
    function changeIt2() public pure returns(address){
        return address(1154414090619811796818182302139415280051214250812);
    }
}

操作上述代码之后,我们发现:我们的地址的确是一个160字节的数字。uint160和地址之间的转换也是相当地丝滑。另外我们可以看到上述代码中的 account1 和 account2 是有一个大小关系的,明显是account1 大于 account2 .是的,没错,我们的地址是可以比较大小的。大家都是有经验的开发人员,在这里我们就不演示这个东东了。

使用钱包转移资金

转账之-payable

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract payableTest{
   
	//payable 关键字代表我们可以通过这个函数给我们的合约充值
    function pay() public payable{
    
    }
}

去掉 payable 关键字之后,我们发现会报错如下:

transact to payableTest.pay errored: VM error: revert.revert The transaction has been reverted to the initial state.
Note: The constructor should be payable if you send value. Debug thetransaction to get more information

翻译过来 就是需要这个关键字的意思。

获取金额-balance

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract payableTest{

    function getBalance() public  view returns(uint){
        return address(this).balance;
    }
}

合约与合约账户

Solidity 中 this 代表合约对象本身,可以通过 address(this) 获取合约地址。

function getThis() public view returns(address) {
	return address(this);
}

部署执行后,我们发现this 就是一串地址。且就等于我们的合约地址。那么就很好理解了,address(this).balance 拿的就是合约里面的money,如此类推的话,可以通过函数的方式拿到账户钱包的地址。

 function getAccountBalance(address account)public view returns(uint){
        return account.balance;
    }

复制钱包地址后执行 getAccountBalance 方法后,我们便可以得到钱包里的余额

 transfer

跨钱包转账

“send”和“transfer”仅适用于“address payable”类型的对象

pragma solidity ^0.6.0;
contract transferTest{
//注意转账 需要添加payable关键字
    function transfer(address payable account) public payable{
        account.transfer(msg.value);
    }
}

我们的操作步骤如下:

  1. 编写转账的智能合约,

  2. 部署,并输入交易金额为50ether

  3. 当前账号选择147,转账目标账户为583

  4. 执行转账操作

  5. 查看结果,最终结果闲置 147账户 50ether,而583账户为350ether(初始为300ether)。

 账户转账到合约

pragma solidity ^0.6.0;
contract transferTest{
    function transfer() payable{
    	this.transfer(msg.value);
    }
    //回调函数
    function () payable{
    
    }
}

全局属性

在solidity中,我们称之为全局变量,这里与我们java,python等语言中全局变量不同,更像是以太坊系统提供给我们的一种系统变量,以方便我们更好地进行转账交易或查询区块链信息等操作。

这里我们重点介绍几个比较重要的以太坊全局变量。

pragma solidity ^0.6.0;
contract globleTest{
    //交易发送者地址
    function getSender() public  view returns(address){
        return msg.sender;
    }
    //获取当前块号的难度
    function getDifficulty()public view returns(uint){
        return block.difficulty;
    }
    //获取当前块号的高度
    function getBlockNumber() public view returns(uint){
        return block.number;
    }
    //获取挖矿矿工地址
    function getCoinbase() public view returns(address){
        return block.coinbase;
    }
}

  • 发送者地址:就是谁给你转钱了。或者是 你给你自己钱包里面的银行卡存钱了。

  • 当前区块的难度:就是假如我给你转钱了,要让所有人相信,区块链网络索要耗费的资源

  • 挖出当前区块的矿工地址: 就是这个矿工帮你记账了 ,系统是不是应该奖励一下他。

  • 当前块号的高度: 就是该笔区块是整个区块链中的第几环咯。

block.blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由 blockhash(uint blockNumber) 代替

block.coinbase (address): 挖出当前区块的矿工地址

block.difficulty (uint): 当前区块难度

block.gaslimit (uint): 当前区块 gas 限额

block.number (uint): 当前区块号

block.timestamp (uint): 自 unix epoch 起始当前区块以秒计的时间戳

gasleft() returns (uint256):剩余的 gas

msg.data (bytes): 完整的 calldata

msg.gas (uint): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替

msg.sender (address): 消息发送者(当前调用)

msg.sig (bytes4): calldata 的前 4 字节(也就是函数标识符)

msg.value (uint): 随消息发送的 wei 的数量

now (uint): 目前区块时间戳(block.timestamp)

tx.gasprice (uint): 交易的 gas 价格

tx.origin (address): 交易发起者(完全的调用链)

转账误操作

在上面的内容中,具体为2.2.1节及4.4.2节中,我们介绍了两种转账方式,并且说明了两者没有什么差别。这里我们把这个内容再放到一起对比下。 这里我们可以看到方式一、二、三都可以给合约账户进行转账,而方式四则可以指定账户进行转账。 不过在给其他账户进行转账s的时候,我们需要注意一点 ,下面来看看: 如果运行界面->交易金额为0时,会报错如下:

pragma solidity ^0.6.0;
contract payableTest{
    // callable函数
     fallback () payable external {}
     receive () payable external {}
    // 方式一 可以直接通过外部输入给合约转账 等同于方式二
    function pay()public  payable{

    }
    // 方式二 通过外部输入给合约转账 等同于方式一
    // 注意:使用this关键字转账 必须写callback函数
    function pay2()public payable{
        address(this).transfer(msg.value);
    }
    //或者 通过payable(x) 把address(this) 转为 address payable 对象
    // function pay2()public payable{
    //   payable(address(this)).transfer(msg.value);
    // }
    
    // 方式三 直接给合约转账
    function pay3()public payable{
        address(this).transfer(10 ether);
    }

    // 方式四 给其他账户转账
    function pay4() public payable{
        address payable account = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
        // account.transfer(10 ether);
        account.transfer(msg.value);
    }
}

这里我们可以看到方式一、二、三都可以给合约账户进行转账,而方式四则可以指定账户进行转账

不过在给其他账户进行转账的时候,我们需要注意一点 ,下面来看看:

// 方式五 给其他账户转账 (慎用 容易引起误操作)
// 必须给运行界面—>交易金额 设置初始转账金额 不能为0
function pay5() payable{
    address account = 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c;
    account.transfer(10 ether);
}

如果运行界面->交易金额为0时,会报错如下

transact to payableTest.pay5 errored: VM error: revert.revert The transaction has been reverted to the initial state.
Note: The constructor should be payable if you send value. Debug the transaction to get more information

语义和句法变化

        现在禁止从外部函数类型到 address 的转换。现在外部函数类型具有一个称为address 的成员属性,类似于现有的 selector 成员。

        对于动态的存储数组,函数 push(value)不再返回长度(现在什么都不返回). 未命名的函数通常称为 fallback function ,已拆分为一个使用 fallback 关键字定义的回退函数 和 使用关键字 receive 定义的接受以太函数。

        如果有`receive”函数,向合约转账的时候会调用,不管是否是调用数据(即便没有以太接收),oreceive 函数隐含带有 payable 新的回退函数会被调用,当没有其他的函数被匹配时,(如果不存在receive函数,则没有调用。数据的转账也会调用回退函数).

        回退函数可以标记为payable(也可以不标记).如果没有payable 那么附加以太的调用会revert。


Solidity 0.6.0之后
        加入了 try/catch 语句 使您可以对失败的外部调用做出反应。
        struct 和 enum 类型可以在文件级别声明。
        数组切片可以用于 calldata 数组,例如:abi.decode(msg.data[4:],(uint,uint))是一个对函数调用payload进行解码底层方法。

        Natspec在开发人员文档中支持多个返回参数,并强制执行与@param 的同名检查。

        数Yulnline Assembl有一个名为leave的新语句,该语句退出当前函数。

        现在可以通过 payable(x)把 address 转换为 address payable,当然 x 需要是 地址类型。

        

receive()函数

一个合约最多有一个 receive 函数, 声明函数为: receive() external payable { ... }

在对合约没有任何附加数据调用(通常是对合约转账)是会执行 receive 函数。例如:通过 .send() 或者 .transfer() 如果 receive 函数不存在,但是有payable的 fallback 回退函数,那么在进行纯以太转账时,fallback 函数会调用.

如果两个函数都没有,这个合约就没法通过常规的转账交易接收以太(会抛出异常)。

fallback()函数

在一个合约中最多有一个fallback()函数,函数声明为: fallback () external [payable]{...},这个函数不能有参数和返回值。(高级版本中可以,文档

作用:当调用一个合约中不存在的函数或者调用空方法时,亦或使用合约地址的内置函数transfer()send() 的时,若没有receive()函数,则会执行目标合约的fallback()函数。

所以在我们对合约账户进行转账的例子中,必须包含fallback() 函数,否则运行时会报错失败。规范写的话则需要包含receive() 函数和 fallback()函数

send方法

我们上节内容可以看到,在我们调用 transfer 方法时,如果说外部输入的交易金额 为0时,则会发生

报错。而这里有一个不报错的方法,你想不想用呢?

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract sendTest1{

  
    function sendTest() public   payable returns(bool){
        address address1 = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372;
        address payable  pa = payable(address1);
        return pa.send(10 ether);
    }
}

但是呢? send 方法在执行时会有一些风险:调用递归不能超过1024

如果gas不够,执行会失败

所以使用 send 方法,要检查成功与否,我们可以根据其返回值是 true 还是 false 进行后续操作,但不

建议。

transfer 相对于 send 方法来说,更加安全,推荐使用。正所谓印证了那句话,人生苦短,我用transfer .

send另一方面沒有,而是為成功/失敗返回真/假。如果您對該故障條件不做任何事情,那麼您會收到該警告

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值