mapping에는 Key와 Value 값이 존재하는데, 어느 상자에 Key를 넣어주면 Value 값이 튀어나온다.
mapping( 키의 타입 => 값의 타입) 접근제한자 변수이름
// SPDX-License-Identifier:GPL-30 pragma solidity >= 0.7.0 < 0.9.0; contract lec17{ mapping(uint256=>uint256) private ageList; }
// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;
contract lec17{
mapping(string=>uint256) private priceList;
mapping(uint256=>string) private nameList;
mapping(uint256=>uint256) private ageList;
function setAgeList(uint256 _key,uint256 _age) public {
ageList[_key] = _age;
}
function getAge(uint256 _key) public view returns(uint256){
return ageList[_key];
}
function setNameList(uint256 _key,string memory _name) public {
nameList[_key] = _name;
}
function getName(uint256 _key) public view returns(string memory){
return nameList[_key];
}
function setPriceList(string memory _itemName,uint256 _price) public {
priceList[_itemName] = _price;
}
function getPriceList(string memory _key) public view returns(uint256){
return priceList[_key];
}
}
위와 같이 setAgeList 처럼 mapping의 키값과 밸류값을 넣어 줄 수 있다. 그리고 반환 시 getAge 처럼 해주면 된다.
참고로 mapping은 length 내장함수가 없기에 length를 구할 수 없다.
배열 안의 값들을 추가, 삭제, 길이 구하기
타입[] 접근제한자 변수명
// SPDX-License-Identifier:GPL-30 pragma solidity >= 0.7.0 < 0.9.0; contract lec18{ uint256[] public ageArray; }
// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;
contract lec18{
uint256[] public ageArray;
uint256[10] public ageFixedSizeArray;
// 미리 사이즈를 넣어줘 제한을 걸기
string[] public nameArray= ["Kal","Jhon","Kerri"];
function AgeLength()public view returns(uint256) {
return ageArray.length;
}
function AgePush(uint256 _age)public{
ageArray.push(_age);
}
function AgeChange(uint256 _index, uint256 _age)public{
ageArray[_index] = _age;
}
function AgeGet(uint256 _index)public view returns(uint256){
return ageArray[_index];
}
function AgePop()public {
ageArray.pop();
}
function AgePop(uint256 _index)public {
delete ageArray[_index];
}
}
지우는 방법 : pop, delete
Pop: 가장 최신의 값을 지운다/ length 도 줄어든다
delete : 원하는 인덱스의 값을 지운다, 그러나 length 는 줄어들지 않는다
delete를 쓰면 원하는 값에 0 이나 null 넣어서 기존의 값을 비워준다, 그렇기때문에 길이는 줄어들지가 않음.
struct 구조체명{
타입 변수명,
타입 변수명,
.....
}// SPDX-License-Identifier:GPL-30 pragma solidity >= 0.7.0 < 0.9.0; contract lec20{ struct Character{ uint256 age; string name; string job; } }
// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;
contract lec20{
struct Character{
uint256 age;
string name;
string job;
}
mapping(uint256=>Character) public CharacterMapping;
Character[] public CharacterArray;
// 위와 같은 식으로 character 타입을 갖는 Mapping과 array를 만들음.
function createCharacter(uint256 _age,string memory _name,string memory _job) pure public returns(Character memory) {
return Character(_age,_name,_job);
}
function createChracterMapping(uint256 _key, uint256 _age,string memory _name,string memory _job ) public {
CharacterMapping[_key] = Character(_age,_name,_job);
}
function getChracterMapping(uint256 _key) public view returns(Character memory){
return CharacterMapping[_key];
}
function createChracterArray(uint256 _age,string memory _name,string memory _job ) public {
CharacterArray.push(Character(_age,_name,_job));
}
function getChracterArray(uint256 _index) public view returns(Character memory){
return CharacterArray[_index];
}
}
character 라는 구조체 안에 age, name, job 이라는 변수ㅜ들이 들어 있다.
다른 언어와 딱히 다를 거 없는 조건문이라 간단히 예문으로만 정리
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract lec21{
/*
Case 1 : if-else
if(if가 발동 되는 조건){
if 내용
}
else{ if 가 발동이 안되면
else 내용
}
Case 2 :if-else if-else
if(if가 발동 되는 조건){
if 내용
}
else if(else if가 발동 되는 조건){
else if 내용
}
...
else{ if, else if 가 발동이 안되면
else 내용
}
*/
string private outcome = "";
function isIt5(uint256 _number) public returns(string memory){
if(_number == 5){
outcome = "Yes, it is 5";
return outcome;
}
outcome = "No, it is not 5";
return outcome;
}
function isIt5or3or1(uint256 _number) public returns(string memory){
if(_number == 5){
outcome = "Yes, it is 5";
return outcome;
}
else if(_number == 3){
outcome = "Yes, it is 3";
return outcome;
}
else if(_number == 1){
outcome = "Yes, it is 1";
return outcome;
}
else{
outcome = "No, it is not 5, 3 or 1";
return outcome;
}
}
}
이도 마찬가지로 다른 언어랑 별 다를 거 없음
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
//for, while, do-while
contract lec22{
/*
for (초기값; 값이 얼마나 forloop을 돌아야하는지; forloop 한번 돌때마다 값의 변화;) {
forloop 내용
}
*/
event CountryIndexName(uint256 indexed _index, string _name);
string[] private countryList = ["South Korea","North Korea","USA","China","Japan"];
function forLoopEvents() public {
for(uint256 i = 0; i<countryList.length; i++){
emit CountryIndexName(i,countryList[i]);
}
}
/*
초기값
while (값이 얼마나 whileloop을 돌아야하는지) {
whileloop 내용
whileloop 한번 돌때마다 값의 변화;
}
*/
function whileLoopEvents() public {
uint256 i = 0;
while(i<countryList.length){
emit CountryIndexName(i,countryList[i]);
i++;
}
}
/*
초기값
do{
dowhileloop 내용
}
while (값이 얼마나 do-while loop을 돌아야하는지)
*/
function doWhileLoopEvents() public {
uint256 i = 0;
do{
emit CountryIndexName(i,countryList[i]);
i++;
}
while(i<countryList.length);
}
}
에러가 발생하면 트랜잭션 중에 발생하는 모든 상태변화를 취소함.
에러 핸들러를 단순하게 말하면, 정의된 조건에 부합하지 않으면(false), 에러를 발생시킨다.
assert : gas를 다 소비한후, 특정한 조건에 부합하지 않으면 에러를 발생시킨다.
revert: 조건없이 에러를 발생시키고, gas를 환불 시켜준다.
require: 특정한 조건에 부합하지 않으면 에러를 발생시키고, gas를 환불 시켜준다.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// 에러핸들러: require, revert, assert, try/catch
contract lec25{
//3000000 gas
function assertNow() public pure{
assert(false); // test
}
//21322 gas
function revertNow() public pure{
revert("error!!"); // if or require = if + revert;
}
//21338 gas
function requireNow()public pure{
require(false,"occurred");
}
function onlyAdults(uint256 _age) public pure returns(string memory){
if( _age < 19){
revert("You are not allowed to pay for the cigarette");
}
return "Your payment is scceeded";
}
function onlyAdults2(uint256 _age) public pure returns(string memory){
require(_age>19,"You are not allowed to pay for the cigarette");
return "Your payment is scceeded";
}
}
assertNow()는 assert(false)를 품고 있는데, 이는 조건문이 false이니 당연히 에러가 발생한다.
assertNow()가 실행이 다 되고 나서 assert문에 의해 에러가 생기고, 가스가 소비가 된 걸 알 수 ㅜ있다.
그렇기에, 주로 assert문은 코드가 정상적으로 작동하는지 테스트 용도로 사용한다.
revert의 형태는 revert("에러 메세지")인데, revert는 특정한 조건문 없이 revert만 들어가면 바로 에러를 발생시킨다.
기존에 봤던 assert와 다르게 gas를 환불해줌.
환불?
gas를 환불해준다라는 뜻이 잘이해 안가실수도 있어요.예를 들어, 함수가있으면 함수의 길이, 어떤식으로 만들어졌는지등에의해서 가스의 가격이 정해지잖아요.
그리고, 저희는 그 함수를 실행할때마다, 정해진 동일한 가스의 가격을 지불하잖아요.그러면 저희가 함수를 실행할때 이런 프로세스 일거예요.
1. 특정 함수를 선택해서 버튼 누른다.
2. 솔리디티는 "이 함수를 돌리고 싶으면, 이만큼의 가스를 지불해라" 라고 요구한다.
3. 가스 비용을 지불한다.
4. 함수 실행중, 어느 부분에서 revert 발생하여 에러가 난다.
5. 에러가 났으니, 함수의 모든 부분이 실행이 안되었으니, 실행 안된만큼 gas를 환불한다.
revert는 코드 삽입만 하면 바로 에러가 뜨니, 딱히 실용성이 없다.
그래서 revert는 주로 if문으로 조건을 주고 사용하거나, 아예 require을 대체하여 사용한다.
require은 revert + if
require(조건문, "에러메세지")
gas를 환불받고 에러메세지도 넣는데, 조건문까지 정의할 수 있어서 특정한 조건에만 에러를 발생시킬 수 있다.
try/catch는 assert/revert/require과는 다르게, 프로그램이 죽는걸 방지한다.
예를들어 기존의 에러 핸들러는, 에러를 발생시켜주고 에러메세지 띄어주고 프로그램을 끝냈지만,
try/catch의 경우는 try 문에서 에러를 잡아 catch 문으로 넘긴후 그 안에서 저희가 에러를 핸들링해주는 코드를 짤 수 있다.
catch 에는 3 가지 종류가 있다.
catch Error(string memory reason) { ... } : revert 나 require을 통해 생성된 에러는 이 catch 에 잡힌다.
catch Panic(uint errorCode) { ... } : 26강에서 봤던 Paninc이네여, assert 를 통해 생선된 에러가 날 때 이 catch에 잡힘. 에러들은, division zero(나누기 0 ), 오버플로우, 배열에 없는 인덱스 접근시 등등이 있다.
catch(bytesmemorylowLevelData){...} : 이 catch는 로우 레벨에러를 잡음.
errorCode
- 0x00: Used for generic compiler inserted panics.
- 0x01: If you call assert with an argument that evaluates to false.
- 0x11: If an arithmetic operation results in underflow or overflow outside of an unchecked { ... } block.
- 0x12; If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0).
- 0x21: If you convert a value that is too big or negative into an enum type.
- 0x22: If you access a storage byte array that is incorrectly encoded.
- 0x31: If you call .pop() on an empty array.
- 0x32: If you access an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
- 0x41: If you allocate too much memory or create an array that is too large.
- 0x51: If you call a zero-initialized variable of internal function type.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract math{
function division(uint256 _num1,uint256 _num2) public pure returns (uint256){
require(_num1<10,"num1 shoud not be more than 10");
return _num1/_num2;
}
}
contract runner{
event catchErr(string _name,string _err);
event catchPanic(string _name,uint256 _err);
event catchLowLevelErr(string _name,bytes _err);
math public mathInstance = new math() ;
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
try mathInstance.division(_num1, _num2) returns(uint256 value){
return(value,true);
} catch Error(string memory _err) {
emit catchErr("revert/require",_err);
return(0,false);
} catch Panic(uint256 _errorCode) {
emit catchPanic("assertError/Panic",_errorCode);
return(0,false);
} catch (bytes memory _errorCode) {
emit catchLowLevelErr("LowlevelError",_errorCode);
return(0,false);
}
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract character{
string private name;
uint256 private power;
constructor(string memory _name, uint256 _power){
name = _name;
power = _power;
}
}
contract runner{
event catchOnly(string _name,string _err);
function playTryCatch(string memory _name, uint256 _power) public returns(bool successOrFail){
try new character(_name,_power) {
return(true);
}
catch{
emit catchOnly("catch","ErrorS!!");
return(false);
}
}
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract runner2{
function simple() public returns(uint256){
return 4;
}
event catchOnly(string _name,string _err);
function playTryCatch() public returns(uint256,bool){
try this.simple() returns(uint256 _value){
return(_value,true);
}
catch{
emit catchOnly("catch","ErrorS!!");
return(0,false);
}
}
}
여기서 주목해야할점은 "this" 라는 키워드.
이 this 는 지금 현재 스마트컨트랙트를 나타내고 있다.
즉 this.simple() 이스마트 컨트랙트의 simple 함수 라는 뜻
이렇게 내부에서 try/catch를 쓰기위해서는 this는 필수적임.
function add(uint256 _num1, uint256 _num2) public pure returns (uint256){
uint256 total = _num1 + _num2;
return total;
}
returns 값에 타입만 써주고 변수를 써주지 않은 경우
function add2(uint256 _num1, uint256 _num2) public pure returns (uint256 total){
total = _num1 + _num2;
return total;
}
returns 부분에 변수를 써준 경우
returns (uint256 total) 에 total 이라고 미리 명시 해줘서, total 변수 명을 새로 명시할 필요가 없음.
uint256 total = _num1 + _num2 를 할 필요 없이
바로 total = _num1 + _num2
그리고, 이런식으로 returns 뒤에 변수명을 명시해주면, 변수가 여러개일때, 어떤 변수인지도 기억하기 좋음