pragma solidity ^0.4.23; // 컴컴파파일러의 버젼
contract Mycontract { // 컨트랙트명
unit count; // 상태변수, 컨트랙트 저장소에 영구히 저장됨
constructor() public { // 생성자
// ...
}
함수이름 매개변수 함수타입 리턴타입
function numOfStudents(address _teacher) public view retunrs(uint) { // 함수
// ...
}
}
is 키워드를 사용하며 여러개를 상속받을 경우 “,” 쉼표 키워드를 사용하여 추가
contract MyNft is ERC721URIStorage, ERC721Enumerable {
...
만약 상속받은 객체들에 동일한 함수가 있다면 나중에 상속받은 객체들부터 검색되어 사용된다.
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Seller{
address private seller;
string private location;
constructor(address _seller){
seller = _seller;
}
function getSeller() public view returns(address){
return seller;
}
}
contract Car{
string private type_; // <-- 예약어라 "_" 붙임
uint8 private door;
uint private price;
constructor(string memory _type, uint8 _door, uint _price) {
type_ = _type;
door = _door;
price = _price;
}
function getDoor() public view returns(uint8) {
return door;
}
function getPrice() public view virtual returns(uint) { // <-- virtual 키워드를 사용하면 자식 클래스에서 오버라이드 할 수 있음
return price;
}
}
contract Benz is Car("suv", 4, 10000), Seller(0x592Ee9A3d490352cF3357b729e399C2B43f9126b) {
string private model;
address private owner;
uint private premium;
constructor(string memory _model, uint _premium){
model = _model;
owner = msg.sender;
premium = _premium;
}
function getModel() public view returns(string memory) {
return model;
}
function getPrice() public view override returns(uint) { // <-- 같은 이름의 부모 클래스 함수를 override 키워드로 오버라이드함
return premium;
}
function getCarPrice() public view returns(uint) {
return super.getPrice(); // <-- 부모 클래스의 함수를 호출할때는 super 키워드를 사용
}
}
타입 | 외부 | 상속 | 내부 |
---|---|---|---|
external | O | X | X |
internal | X | O | O |
public | O | O | O |
private | X | X | O |
pragma solidity ^0.4.23;
contract Mycontract {
unit external count; // 상태변수에는 external 사용불가
constructor() public {
// ...
}
function numOfStudents(address _teacher) public view retunrs(uint) {
test(); // 컨트랙 내부에서 사용 불가
}
function test() external {
// ...
}
}
constract YourContract {
MyContract myContract;
function callTest() public {
myContract.test(); // 외부 컨트랙이므로 test() 호출 가능
}
}
pragma solidity ^0.4.23;
contract Mycontract {
unit count;
constructor() public {
// ...
}
function numOfStudents(address _teacher) public view retunrs(uint) {
test(); // 컨트랙 내부에서 호출 가능
}
function test() internal {
// ...
}
}
constract YourContract is MyContract { // is MyContract ← 상속을 의미
function callTest() public {
test(); // 상속받은 컨트랙에서도 test() 호출 가능
}
}
pragma solidity ^0.4.23;
contract Mycontract {
unit public count;
constructor() public {
// ...
}
function numOfStudents(address _teacher) public view retunrs(uint) {
test(); // 컨트랙 내부에서 호출 가능
}
function test() public {
// ...
}
}
constract YourContract is MyContract { // is MyContract ← 상속을 의미
function callTest() public {
test(); // 상속받은 컨트랙에서도 test() 호출 가능
}
}
constract HisContract {
MyContract myContract;
function callTest() public {
myContract.test(); // 외부 컨트랙에서도 test() 호출 가능
}
}
pragma solidity ^0.4.23;
contract Mycontract {
unit public count;
constructor() public {
// ...
}
function numOfStudents(address _teacher) public view retunrs(uint) {
test(); // 컨트랙 내부에서 호출 가능
}
function test() public {
// ...
}
}
constract YourContract is MyContract {
function callTest() public {
test(); // 상속받은 컨트랙에서는 test() 호출 불가
}
}
constract HisContract {
MyContract myContract;
function callTest() public {
myContract.test(); // 외부 컨트랙에서도 test() 호출 불가
}
}
타입 | 설명 | 가스 비용 |
---|---|---|
view | 데이터 read-only | 없음 |
pure | 1) 데이터 읽지 않음 2) 인자 값만 활용해서 반환 값 정함 | 없음 |
constant | 0.4.17 버전 이전에는 view/pure 대신 쓰임 | 없음 |
payable | 함수가 ETH를 받을 수 있게 함 | 있음 |
uint numOfStudents;
function getNumOfStudents() public view returns (uint) {
return numOfStudents;
}
블록체인에 저장된 numOfStudents만 리턴하는 읽기 전용 함수.
numOfStudents를 수정하거나 삭제할 수 없음
function multiply(uint x, uint y) public pure returns (uint) {
return x * y;
}
uint numOfStudents
function getNumOfStudents() public constant returns (uint) {
return numOfStudents;
}
function buy() public payable {
require(10000 = msg.value);
transferEther(msg.sender);
}
msg.value는 송신자가 이더를 얼마나 보냈는지 wei 값으로 맵핑되어있다.
분류 | 타입 | 값 |
---|---|---|
Boolean | bool | true / false |
bool x = false;
분류 | 타입 | 값 |
---|---|---|
정수형 | int | 8 bit ~ 256 bit |
정수형 | uint | 8 bit ~ 256 bit |
// int
// int == int256
// 양수와 음수 모두 사용가능
// 사용할 숫자 범위가 파악이 가능하면 되도록 비트를 지정해줘서 사용하는것이 최적화 부분에서 좋음
int32 x = -27467
int x = -27456
// uint
// uint == uint256
// 양수만 사용가능
// 사용할 숫자 범위가 파악이 가능하면 되도록 비트를 지정해줘서 사용하는것이 최적화 부분에서 좋음
uint256 = 24557867
uint = 24557867
분류 | 타입 | 값 |
---|---|---|
주소형 | address | 20 byte 값 이더리움 계정 주소 |
// adress 타입은 balance, transfer 두 개의 멤버를 소유한다.
// balance: 해당 계정의 잔액
// transfer: 해당 계정으로 이더 전송
adress x = 0x123;
function send() public {
if (x.balance < 10) {
x.transfer(10);
}
}
분류 | 타입 | 값 |
---|---|---|
고정된 크기의 byte 배열 | byte | 1 byte ~ 32 byte |
// byte == bytes1
// 바이트 타입
// 솔리디티는 스트링에 최적화되어있지 않고 32바이트 문자열에 최적화 되어있다.
// 따라서 문자를 저장할때는 hex로 변환해서 저장
// 32바이트를 넘기지 않는 길이라면 바이트 타입을 사용한다.
bytes32 x = “0x68656c6f20776f726c64”;
// 스트링타입
// 32바이트를 초과할때는 스트링 타입을 쓴다.
// 스트링 타입은 바이트 타입보다 가스 비용이 더 요구된다.
bytes32 x = “hello world”;
분류 | 타입 | 값 |
---|---|---|
동적인 크기의 byte 배열 | bytes | 무한 |
// bytes/string은 값 타입이 없다.
bytes[] names;
분류 | 타입 | 값 |
---|---|---|
열거형 | enum | 이름 { value1, value2} |
// enum은 사용자 정의 타입 중 하나
// 값을 정수형으로 리턴함
enum Direction { Right, Left} // 인덱스 0: Right, 1: Left
Direction direction;
function getDirection() public return (uint) {
return uint(direction); // Direction의 인덱스를 리턴, 인덱스는 0 부터 시작됨
}
function setDirection(uint newDirection) public {
direction = Direction(newDirection); // 매개변수를 받은 숫자를 인자로 사용해 값을 지정
}
자료형 | ex1 | ex2 | 범위 | 비고 |
---|---|---|---|---|
bool | true / false | |||
int8 | int8 public data = -1 | 3 | -2^7 ~2^7 -1, -128 ~ 128 | |
uint8 | uint8 public data = 1 | 0~2^8 -1 , 255 | 양수만 가능 | |
int (int256) | int public data = -2222 | 22222 | -2^255 ~2^255 -1 | |
uint (uint256) | uint public data = 22222 | 0~2^256 -1 | 양수만 가능 | |
string | 내부적으로 바이트 값으로 저장이됨 | |||
bytes | - 데이터가 들어오는 범위가 어느정도인지 가늠되지 않아서 가변적으로 메모리를 사용하려할 때 bytes를 사용 | |||
bytes{숫자} | bytes32 public tx = hex”0x5da17b4d19e21ddf5136fe2188a5a92c51a4f868597a0c618537f102a7c548f9” | bytes20 public address = hex”0xFa0CaCa91a26F421813503f577cD95f115CFdf41” | ~bytes32 | - byte 값이 고정되어 있는 경우에는 숫자를 붙여 고정된 범위를 사용, 주소값을 쓸때 bytes20, 트랜잭션 해시 값을 쓸때bytes32를 사용 |
address | address public wallet = “0xFa0CaCa91a26F421813503f577cD95f115CFdf41” | bytes20과 동일하다고 볼 수 있음, hex를 안 붙여도 됨 |
솔리디티에서는 float 타입이 없어 소수점을 사용할 수 없음, 외부에서 처리해줘야 함
- string과 bytes
- String은 유니코드 단위, 즉 2byte단위
- byte는 1byte단위
- string은 인코딩이 되어 있어서 다른 것으로 전환할때 깨지기 쉽지만 별다른 처리가 필요 없다는 점이 장점
- byte는 인코딩이 안된 binary라서 깨지지는 않지만 해당 byte가 어떤 방식으로 인코딩되어야 할지 정확하게 알고 있어야 하고 그것을 문자열로 변환 해주어야 하기 때문에 번거로운 단점이 있음
contract MyContract {
uint[] ages; // storage
// [11, 22, 33]
function learnDataLocation(uint[] memory newAges) public returns (uint a) {
// newAge memory 배열을 ages storage 배열에 복사.
// ages 값: [11, 22, 33]
ages = newAges;
// int, uint, bool과 같은 기본 타입들은 함수안에 로컬 변수로 선언되었을때 memory
uint16 myAge = 44;
// 배열은 함수안에 로컬 변수로 쓰일때 storage
// studentAges는 ages를 가리키는 포인터
// studentAges 값: [11, 22, 33]
uint[] studentAges = ages;
// studentAges 배열의 첫 번째 인덱스를 44로 바꿈
// studentAges 값: [44, 22, 33]
// ages 값: [44, 22, 33]
studentAges[0] = myAge;
// 44를 리턴 변수에 대입, a는 메모리
a = studentAges[0];
return a;
}
}
contract MyContract {
uint[] myArray; // 동적배열, 함수 밖 상태변수는 디폴트로 storage
uint8[3] d = [1, 2, 3]; // 정적배열, 함수 밖 상태변수는 디폴트로 storage
function learnArrays() public {
// 배열은 로컬(지역) 변수로 쓸때 디폴트 storage 인데
// memory 지시어를 통해 강제로 memory로 변경
uint256[] memory a = new uint256[](5); // 배열 사이즈 5로 지정
bytes32[] memory b = new bytes32[](10); // 배열 사이즈 10으로 지정
// 메모리 배열은 사이즈를 다시 정하지 못함을 유의
// a 배열 첫 번째 인덱스 숫자 1 대입
a[0] = 1;
// a 배열 두 번째 인덱스 숫자 2 대입
a[1] = 2;
// 배열의 사이즈를 3으로 고정
// 함수안에서 [1, 2, 3] 리터럴 초기화
// 함수 안에서 리터럴로 배열 초기화시 무조건 memory로 지정시켜야함, 안그러면 에러 발생
uint8[3] memory c = [1, 2, 3];
// 함수 안의 리터럴 초기화인데 memory로 지정시키지 않았으므로 에러가 발생됨
// 4라인처럼 함수 밖에서 리터럴로 배열 초기화시에는 memory 선언 안해도됨
uint8[3] d = [1, 2, 3];
// 동적 배열에는 push() 멤버 메소드 사용 가능
// 또한 push()는 storage만 가능하고 memory는 사용 불가
myArray.push(5);
// length() 멤버 메소드는 storage, memory 둘 다 사용 가능
uint myArrayLength = myArray.length; // 1 대입
}
}
contract MyContract {
struct Student {
string studentName;
string gender;
uint age;
}
Student[] students;
function addStudent(string _name, string _gender, uint _age) public {
// 상태변수 students 배열에 새로운 Student 입력
students.push(Student(_name, _gender, _age));
// storage에 저장하는 새로운 Student 선언
// 상태변수 students 배열의 첫 번째 인덱스 값 대입
// storage로 선언되었기 때문에 상태 변수를 가르키는 포인터 역할
Student storage updateStudent1 = student[0];
// updateStudent1의 age 필드 55로 변경
// 결과적으로 상태변수 students 배열의 첫 번째 인덱스의 age 필드를 55로 변경함
updateStudent1.age = 55;
// memory에 저장하는 새로운 Student 선언
// 상태변수 students 배열의 첫 번째 인덱스 값 대입
// memory로 선언됐기 때문에 값의 복사
Student memory updateStudent2 = students[0];
// updateStudent2의 age 필드를 20으로 변경
// 결과적으로는 updateStudent2의 age 필드를 20으로 변경함
updateStudent2.age = 20;
// memory 배열의 값을 상태변수에 직접적으로 대입해주면 students 값은 영구히 변경
students[0] = updateStudent2;
}
}
contract MyContract {
// 키 타입 밸류 타입 변수명
mapping(address => uint256) balance;
function learnMapping() public {
// msg.sender는 현재 이 함수를 불러오는 계정 주소를 뜻한다
balance[msg.sender] = 100;
balance[msg.sender] += 100; // 값의 변경도 가능
// balance 매핑타입 변수의 밸류타입이 uint256이기 때문에
// balance 밸류값을 받기 위해서
// currentBalance의 타입을 uint256으로 지정
uint256 currentBalance = balance[msg.sender];
}
}
contract MyContract2 {
struct Student {
string studentName;
string gender;
uint age;
}
mapping(uint256 => Student) studentInfo;
function setStudentInfo(uint _studentId, string memory _name, string memory _gender, uint _age) public {
// 매개변수 _studentId(예: 1234)를 mapping 변수 studentInfo의 키값으로 사용
// 1234 키값의 특정 Student 구조체 정보를 불러온다
Student storage student = studentInfo[_studentId];
student.studentName = _name;
student.gender = _gender;
student.age = _age;
}
// 솔리디티에서 아직까지는 리턴값으로 구조체를 지원하지 않음
function getStudentInfo(uint256 _studentId) view public returns (string memory, string memory, uint) {
// 매개변수로 받은 키값으로(1234) 맵핑된 value값인 Student를 리턴
return (studentInfo[_studentId].studentName
, studentInfo[_studentId].gender
, studentInfo[_studentId].age);
}
}
uint wei = 1 wei;
uint eth = wei * (10**18);
// or
uint eth = 1000000000000000000
// or
uint eth = 1 ether;
uint8 sum = 0;
for(uint8 i=1; i < 11; i++) {
sum += 1;
}
uint8 sum = 0;
uint8 i = 1;
while(i<11) {
sum +1;
i++;
}
uint8 sum = 0;
uint8 i = 1;
do{
sum += 1;
i++;
}while(i<11)
uint8 sum = 0;
for(uint8 i=1; i < 11; i++) {
sum += 1;
if(sum > 10){
break;
}
}
uint8 sum = 0;
for(uint8 i=1; i < 11; i++) {
if(i == 5){
continue;
}
sum += 1;
}
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
contract BlockProperty{
uint public basefee = block.basefee;
uint public chainid = block.chainid;
address payable public coinbase = block.coinbase;
uint public difficulty = block.difficulty;
uint public gaslimit = block.gaslimit;
uint public blockNumber = block.number;
uint public timestamp = block.timestamp;
}
테스트 넷이라 메인넷이랑 차이가 있을 수 있음
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
contract BlockProperty2{
function generateRandom() public view returns(uint8) {
// 내장 함수인 keccak256()을 사용하여 SHA3계열 해쉬 알고리즘을 사용할 수 있다.
// 파라메터는 바이트 값으로 넣어줘야 하기 때문에 abi.encodePacked()라는 내장 함수를 이용한다.
// uint256()값은 너무 크기 때문에 %251 연산을 통해서 251보다 작은 값이 나오도록 하였다.
// 블록 정보가 업데이트될때마다 다른 값이 출력되게된다.
uint8 number = uint8(uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty)))%251);
return number;
}
}
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract TransactionProperty{
// 남은 가스를 확인 가능, 이를 통해 트랜잭션을 계속 진행할지 말지를 결정할때 사용 할 수 있음.
uint public gasLeft = gasleft();
// 트랜잭션 함수를 호출할때 데이터 값(Input Data)이 들어가게되는데, 그 데이터 값을 확인할 수 있음
bytes public msgData = msg.data;
// 트랜잭션을 요청한 계정 주소
address public sender = msg.sender;
// 함수를 호출할때 함수의 MethodID 값이 인풋 데이터에 들어가게 되는데 그 바이트 코드 값을 리턴한다.
bytes4 public sig = msg.sig;
// 사용자가 전송한 이더의 양을 리턴, payable 함수 안에서 확인 가능
uint public msgValue = msg.value;
}
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract TransactionProperty2{
// 요청한 사용자 주소를 저장하는 경우
mapping(address=>uint) private orderList;
function newOrderList() external payable {
orderList[msg.sender] = msg.value;
}
// 사용자가 호출한 함수를 확인하는 경우
// MethodID는 함수명을 keccak256() 해시 함수의 인자로 전달하여 해시한 후 바이트 값으로 바꾼것이다.
// 파라메터가 있는 함수의 경우 추가적인 데이터를 조합한다.
function newCheckFunction() public pure returns(bool){
bytes4 selector = bytes4(keccak256("newCheckFunction()"));
return selector == msg.sig;
}
}
require()
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Modifier{
uint public minPrice = 10000;
mapping (address=>int) public orderList;
function test1() public payable{
// 전송 금액이 minPrice 보다 클 경우에만 진행이 계속 될 수 있다.
// 조건이 참일 경우에만 진행된다.
// 실행할때까지 발견할 수 없는 유효 조건 검사를 위해 사용된다.
// 조건에 통과하지 못한다면 에러메세지와 함께 에러를 발생시키고 트랜잭션을 롤백 후 가스비가 환불된다.
require(msg.value > minPrice, "에러 메세지를 정의");
orderList[msg.sender] = msg.value;
}
}
modifier
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Modifier{
uint public minPrice = 10000;
mapping (address=>int) public orderList;
// 앞선 test1 함수의 기능을 아래와 같이 modifier를 사용하여 구현할 수 있다.
// 하나의 모디파이어를 공통으로 사용할때 유용하다.
modifier checkMinPrice() {
require(msg.value > minPrice);
_; // <-- 이 부분의 의미는 위 조건을 실행 후 다음 코드를 이어나간다는 의미라고 볼 수 있다.
}
function test2() public payable checkMinPrice{ //<-- 모디파이어를 적용
orderList[msg.sender] = msg.value;
}
}
assert()
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Assert{
// require문과의 차이점은 에러 메세지를 사용하지 않고 단순히 조건만 체크한다는 점이다.
// 조건을 통과하지 못할 경우 에러를 발생시키고
// 트랜잭션으로 인해 지금까지 변경된 state를 rollback 시키고
// 사용자가 제공한 가스 프라이스를 되돌려준다. (0.8.0 이하는 환불 안됨)
// assert는 개발을 할때 내부 오류를 테스트하고 불변성을 확인하는 경우에 사용한다고한다.
function order() public payable{
assert(msg.value != 0);
}
}
revert()
function order() external {
// revert는 조건 없이 에러를 발생시키고 state 롤백, 가스비 환불
// if/else 문과 함께 사용됨.
revert("Error msg");
}
보통 require나 revert를 사용하고 assert는 개발을 할때 내부 오류를 테스트하고 불변성을 확인하는 경우에 사용한다고한다.
- 참고링크
[[solidity] Error Handling (assert, require, revert)](https://velog.io/@imysh578/solidity-Error-Handling-assert-require-revert)
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Math {
function plus(uint a, uint b) external returns(uint) {
return a + b;
}
}
contract TryCatch{
Math math = new Math();
address payable temp_address = "0x592Ee9A3d490352cF3357b729e399C2B43f9126b";
function callOtherContract(address to) external {
// try catch는 다른 컨트랙트를 쓸때만 사용 가능하다
try math.plus(6, 4) returns(uint plusResult) {
(temp_address).transfer(plusResult);
}catch{
revert();
}
}
}
//SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <0.9.0;
import "hardhat/console.sol";
//error ZeroCheckError(); // <-- 컨트랙트 바깥쪽에서도 사용 가능하다
contract CustomError{
// require, revert를 쓰다보면 에러 문구를 반복적으로 사용하게되는데 이럴때 custom error를 사용하면 유용하다.
error ZeroCheckError(); // <-- 컨트랙트 안쪽에서도 바깥쪽에서도 사용 가능하다
mapping (address=>uint) public testMap;
function test1() external payable{
if(msg.value < 1000){
revert ZeroCheckError();
}
testMap[msg.sender] = msg.value;
}
function test2() external payable{
if(msg.value < 2000){
revert ZeroCheckError();
}
}
}