url: https://solidity-by-example.org/error/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Error {
function testRequire(uint _i) public pure {
// Require should be used to validate conditions such as:
// - inputs
// - conditions before execution
// - return values from calls to other functions
require(_i > 10, "Input must be greater than 10");
}
function testRevert(uint _i) public pure {
// Revert is useful when the condition to check is complex.
// This code does the exact same thing as the example above
if (_i <= 10) {
revert("Input must be greater than 10");
}
}
uint public num;
function testAssert() public view {
// Assert should only be used to test for internal errors,
// and to check invariants.
// Here we assert that num is always equal to 0
// since it is impossible to update the value of num
assert(num == 0);
}
// custom error
error InsufficientBalance(uint balance, uint withdrawAmount);
function testCustomError(uint _withdrawAmount) public view {
uint bal = address(this).balance;
if (bal < _withdrawAmount) {
revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount});
}
}
}
에러는 트랜잭션 중 상태에 대한 모든 변경 내역을 취소시킨다.
require, revert, assert를 호출하여 에러를 발생시킬 수 있다.
function testRequire(uint _i) public pure {
// 입력 값이 10 이하인 경우, 에러 메세지를 반환하고 종료
require(_i > 10, "Input must be greater than 10");
}
function testRevert(uint _i) public pure {
// 입력 값이 10 이하인 경우
if (_i <= 10) {
//에러 메세지를 반환하고 종료
revert("Input must be greater than 10");
}
}
function multiple(uint num1, uint num2) public pure returns (uint) {
require(num1 % 2 == 0 || num2 % 2 == 0, "input error");
uint result = num1 * num2;
assert(result % 2 == 0);
return result;
}
require 함수는 외부로부터 들어오는 입력값에 대한 확인, assert는 함수 내부에 있는 값을 바탕으로 실행된 코드에 문제가 없는지 확인한다.
url: https://solidity-by-example.org/function-modifier/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract FunctionModifier {
// We will use these variables to demonstrate how to use
// modifiers.
address public owner;
uint public x = 10;
bool public locked;
constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}
// Modifier to check that the caller is the owner of
// the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
// Underscore is a special character only used inside
// a function modifier and it tells Solidity to
// execute the rest of the code.
_;
}
// Modifiers can take inputs. This modifier checks that the
// address passed in is not the zero address.
modifier validAddress(address _addr) {
require(_addr != address(0), "Not valid address");
_;
}
function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
owner = _newOwner;
}
// Modifiers can be called before and / or after a function.
// This modifier prevents a function from being called while
// it is still executing.
modifier noReentrancy() {
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}
function decrement(uint i) public noReentrancy {
x -= i;
if (i > 1) {
decrement(i - 1);
}
}
}
함수 변경자 (function modifier)는 한글로 함수 제어자 라고 표현하기도 한다. 아마도, visibility를 자바에서 접근 제어자라고 하듯이, 이해가 쉽고 유사한 기능이라 그런 것 같다.
함수 변경자는 사용자 정의 함수 변경자를 선언하여 사용하거나, pure, view와 같이 내장된 기능을 사용할 수도 있다.
url: https://solidity-by-example.org/events/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Event {
// Event declaration
// Up to 3 parameters can be indexed.
// Indexed parameters helps you filter the logs by the indexed parameter
event Log(address indexed sender, string message);
event AnotherLog();
function test() public {
emit Log(msg.sender, "Hello World!");
emit Log(msg.sender, "Hello EVM!");
emit AnotherLog();
}
}
event는 이더리움 블록체인에 로깅할 수 있다. 이벤트의 사용 사례는 다음과 같다.
{
// 이벤트 선언
event Log(address indexed sender, string message);
// 이벤트 발생
emit Log(msg.sender, "Hello World!");
}
indexed는 블록안에 출력된 이벤트들을 필터링하여 원하는 이벤트만을 가져오는데 사용할 수 있다.
참고자료: https://velog.io/@octo__/Solidity-event-%EC%99%80-indexed-%ED%82%A4%EC%9B%8C%EB%93%9C
url: https://solidity-by-example.org/constructor/
참고자료: https://caileb.tistory.com/137
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Base contract X
contract X {
string public name;
constructor(string memory _name) {
name = _name;
}
}
// Base contract Y
contract Y {
string public text;
constructor(string memory _text) {
text = _text;
}
}
// There are 2 ways to initialize parent contract with parameters.
// Pass the parameters here in the inheritance list.
contract B is X("Input to X"), Y("Input to Y") {
}
contract C is X, Y {
// Pass the parameters here in the constructor,
// similar to function modifiers.
constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
}
// Parent constructors are always called in the order of inheritance
// regardless of the order of parent contracts listed in the
// constructor of the child contract.
// Order of constructors called:
// 1. X
// 2. Y
// 3. D
contract D is X, Y {
constructor() X("X was called") Y("Y was called") {}
}
// Order of constructors called:
// 1. X
// 2. Y
// 3. E
contract E is X, Y {
constructor() Y("Y was called") X("X was called") {}
}
Constructor (생성자)는 컨트랙트 작성 시 선택적으로 정의할 수 있다. 생성자 함수는 컨트랙트가 실행된 후 최초 1회만 동작한다.
// Base contract X
contract X {
string public name;
constructor(string memory _name) {
name = _name;
}
}
contract X는 Contract 배포 시 생성자 함수 매개변수 _name을 입력해야 한다.
생성자 함수 실행 후 name 프로퍼티에 값이 저장된다.
contract C is X, Y {
constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
}
contract C는 contract X, Y를 상속하고 부모 contract의 생성자 함수를 호출함으로써 부모 contract를 초기화할 수 있다.
contract C is X, Y {
constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
function getName () view public returns (string memory){
return name;
}
function getText () view public returns (string memory){
return text;
}
}
상속받는 베이스 컨트랙트의 프로퍼티를 internal로 수정해서 외부에서 값을 접근하지 못하게 하고, getter 함수를 작성해서 테스트했다.
// Order of constructors called:
// 1. X
// 2. Y
// 3. D
contract D is X, Y {
constructor() X("X was called") Y("Y was called") {}
}
// Order of constructors called:
// 1. X
// 2. Y
// 3. E
contract E is X, Y {
constructor() Y("Y was called") X("X was called") {}
}
상속 순서에 따라 부모 생성자 호출 순서가 정해진다.
url: https://solidity-by-example.org/inheritance/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/* Graph of inheritance
A
/ \
B C
/ \ /
F D,E
*/
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
// Contracts inherit other contracts by using the keyword 'is'.
contract B is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// Contracts can inherit from multiple parent contracts.
// When a function is called that is defined multiple times in
// different contracts, parent contracts are searched from
// right to left, and in depth-first manner.
contract D is B, C {
// D.foo() returns "C"
// since C is the right most parent contract with function foo()
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}
contract E is C, B {
// E.foo() returns "B"
// since B is the right most parent contract with function foo()
function foo() public pure override(C, B) returns (string memory) {
return super.foo();
}
}
// Inheritance must be ordered from “most base-like” to “most derived”.
// Swapping the order of A and B will throw a compilation error.
contract F is A, B {
function foo() public pure override(A, B) returns (string memory) {
return super.foo();
}
}
Solidity는 다중 상속을 지원한다.
상속은 is 키워드를 사용해서 다른 컨트랙트를 상속할 수 있다.
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
contract B is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
컨트랙트 B는 is 키워드를 사용하여 A 컨트랙트를 상속한다.
상속하는 컨트랙트에서 재정의하여 사용되는 함수는 virtual 키워드로 선언한다.
virtual 함수를 재정의 할 경우, virtual override 키워드를 사용한다.
contract D is B, C {
// D.foo() returns "C"
// since C is the right most parent contract with function foo()
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}
is B C 상속 순서에 따라 super.foo()의 결과는 C가 나온다. super는 상속한 부모 컨트랙트를 참조하는 키워드
contract F is A, B {
function foo() public pure override(A, B) returns (string memory) {
return super.foo();
}
}
상속은 기본 형태부터 (most base-like) 가장 파생된 순서 (most derived)형태여야 한다.
A와 B의 상속 순서를 변경하면 컴파일 에러가 발생한다.
url: https://solidity-by-example.org/shadowing-inherited-state-variables/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract A {
string public name = "Contract A";
function getName() public view returns (string memory) {
return name;
}
}
// Shadowing is disallowed in Solidity 0.6
// This will not compile
// contract B is A {
// string public name = "Contract B";
// }
contract C is A {
// This is the correct way to override inherited state variables.
constructor() {
name = "Contract C";
}
// C.getName returns "Contract C"
}
위에서 살펴본 것 처럼, 상속한 컨트랙트에서 부모의 virtual function을 오버라이딩해서 재정의할 수 있지만 변수는 불가능하다.
따라서,상속된 상태 변수를 올바르게 재정의하기 위해서 생성자 함수에서 값을 변경할 수 있다.
url: https://solidity-by-example.org/super/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/* Inheritance tree
A
/ \
B C
\ /
D
*/
contract A {
// This is called an event. You can emit events from your function
// and they are logged into the transaction log.
// In our case, this will be useful for tracing function calls.
event Log(string message);
function foo() public virtual {
emit Log("A.foo called");
}
function bar() public virtual {
emit Log("A.bar called");
}
}
contract B is A {
function foo() public virtual override {
emit Log("B.foo called");
A.foo();
}
function bar() public virtual override {
emit Log("B.bar called");
super.bar();
}
}
contract C is A {
function foo() public virtual override {
emit Log("C.foo called");
A.foo();
}
function bar() public virtual override {
emit Log("C.bar called");
super.bar();
}
}
contract D is B, C {
// Try:
// - Call D.foo and check the transaction logs.
// Although D inherits A, B and C, it only called C and then A.
// - Call D.bar and check the transaction logs
// D called C, then B, and finally A.
// Although super was called twice (by B and C) it only called A once.
function foo() public override(B, C) {
super.foo();
}
function bar() public override(B, C) {
super.bar();
}
}
solidity는 super 키워드를 사용해서 부모 컨트랙트를 호출할 수 있다.
event Log(string message);
function foo() public virtual {
emit Log("A.foo called");
}
function bar() public virtual {
emit Log("A.bar called");
}
이벤트는 event 키워드를 사용해서 선언하고, emit 키워드를 통해 이벤트를 방출할 수 있다. 이벤트는 트랜잭션 로그에 기록되어 함수 호출을 추적하는데 유용하게 사용할 수 있다.
contract B is A {
function foo() public virtual override {
emit Log("B.foo called");
A.foo();
}
function bar() public virtual override {
emit Log("B.bar called");
super.bar();
}
}
컨트랙트 B를 배포하여 bar() 함수를 호출하면 아래와 같이 B, A 순으로 호출된다.
emit Log("B.bar called");
emit Log("A.bar called");
컨트랙트 D는 컨트랙트 B, C를 상속한다.
contract D is B, C {
function foo() public override(B, C) {
super.foo();
}
function bar() public override(B, C) {
super.bar();
}
}
이 때, D.foo()를 호출한 경우 아래와 같이 상속 순서에 따라 오버라이딩과 함수가 호출된다.
function foo() public override(B, C) {
super.foo();
}
super 키워드는 가장 마지막에 상속받은 컨트랙트를 가르킨다. 따라서, B -> C 상속 순서에 따라 C.foo()가 호출된다.
Contract C is A {
function foo() public virtual override {
emit Log("C.foo called");
A.foo();
}
}
따라서, C.foo()가 실행되어 아래와 같은 결과가 로그로 출력된다.
contract D is B, C {
function bar() public override(B, C) {
super.bar();
}
}
D.bar()는 super 키워드를 통해 가장 마지막에 상속받은 C.bar()를 호출한다.
contract C is A {
function bar() public virtual override {
emit Log("C.bar called");
super.bar();
}
}
C.bar()는 마찬가지로 B.bar()를 호출하고, B.bar()는 A.bar()를 호출하게 된다.