[솔리디티] 기초 문법 - 5

근상·2025년 1월 7일

솔리디티 기초

목록 보기
6/7

이번 시간에
컨트랙트 상속이다.

두 방식을 잘 이해해 보자


Inheritance

기본적인 상속을 알아보겠다.

예를들어 유명한 독일 자동차 두 회사를 예로 들어보자.

contract Benz {
	string private _type;
    uint8 private door;
	string private model;
    address private owner;

    function getModel() public view returns(string memory) {
        return model;
    }
    
    function getDoor() public view returns(uint8) {
        return door;
    }

}

contract Bmw {
	string private _type;
    uint8 private door;
	string private model;
    address private owner;

    function getModel() public view returns(string memory) {
        return model;
    }
    
    function getDoor() public view returns(uint8) {
        return door;
    }

}

이 경우가 있다.
하지만 더 큰 범주에서 보면
변수에서 자동차 자체의 문과, 타입(세단, SUV 등)은 회사랑 관련없고
함수에서는 getDoor()는 더 큰 범주에 해당된다.

그래서 이를 Car라는 컨트랙트를 하나 더 생성해 상속시켜보자.

contract Car {
	string private _type;
    uint8 private door;
    
    constructor(string memory type_, uint8 _door) {
    	_type = type_;
        door = _door;
    }
    
    function getDoor() public view returns(uint8) {
    	return door;
    }
}

contract Benz is Car{
	string private model;
    address private owner;

    function getModel() public view returns(string memory) {
        return model;
    }
}

contract Bmw is Car{
    address private owner;

    function getModel() public view returns(string memory) {
        return model;
    } 
}

우선은 간단하게 이런 방식으로 is {Contract Name}을 통해
컨트랙트를 상속할 수 있다.

그리고 벤츠나 BMW 내용이 같으니 둘 중 한 컨트랙트를 Remix에서 배포를 하면

두 함수 모두 사용 가능함을 보면 상속이 제대로 됐다는 것을 알 수 있다.


Inheritance Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Car {
    string private type_;
    uint8 private door;

    constructor(string memory _type, uint8 _door) {
        type_ = _type;
        door = _door;
    }

    function getDoor() public view returns(uint8) {
        return door;
    }
}

contract Benz is Car("SUV", 4){
    string private model;
    address private owner;

    constructor(string memory _model) {
        model = _model;
        owner = msg.sender;
    }

    function getModel() public view returns(string memory) {
        return model;
    }
}

Car 컨트랙트와, Benz 컨트랙트 모두 constructor()를 이용한다.
Benz컨트랙트를 생성할 때 부모컨트랙트인 Car constructor에 넣을 내용을 입력해준다.

그리고 Benz컨트랙트를 배포하려고 했을 때 _model을 채워준다.
그 후 다시 한 번 두 함수가 나타난 것을 보고 상속이 잘 됐다는 것을 알 수 있고
두 함수 모두 실행했을 때 원하는 결과가 나오는 것을 보고 상속 활용도 잘 된 것을 알 수 있다.


Overriding

상속을 하면서 중복되는 경우가 있다. 이를 overriding이라고 부른다.

우선 에러가 나는 경우를 보자

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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 returns(uint) {
        return price;
    }
}

contract Benz is Car("SUV", 4, 10000){
    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 returns(uint) {
        return premium;
    }
}

전 코드와 거의 유사하지만 부모, 자식 모두 getPrice()함수가 추가됐고
변수도 하나씩 늘었다.
하지만 컴파일 해보면 알겠지만 에러가 발생한다.

이럴 때 부모 함수에는 virtual, 상속 받는 함수에는 override를 쓰면 컴파일이된다.

부분 코드만 보면

 // 부모 컨트랙트 내 함수
 function getPrice() public view virtual returns(uint) {
        return price;
 }
    
// 자식 컨트랙트 내함수
function getPrice() public view override returns(uint) {
	return premium;
}

그래서 동일한 함수를 쓰고 싶을 때 모 함수와 자식 함수를 이를 통해
나눠야 한다.

이렇게 배포를 하면

잘 봐야될 점은 virture와 override 사용을 통해 각 getPrice()가 정의됐고
모 함수에는 10000, 자식 함수에는 1000을 넣고 확인해보면
1000이 리턴된 것을 보고, 자식 함수가 호출됐음을 알 수 있다.

그래서 상속관계에 놓인 컨트랙트 사이에서 함수명이 만약 동일하다면
이와 같은 방법을 통해 해결할 수 있다는 것을 기억하자.


Super

하지만 다른 경우로, 위와 같이 부모 자식 함수를 나눈 상태에서
모 함수를 쓰고 싶은 경우도 있다.

이때 사용하는 키워드가 super다.

 function getCarPrice() public view returns(uint) {
        return super.getPrice();
    }
 // 벤츠 컨트랙트 마지막줄에 이 내용을 추가. 모 함수를 사용하고 싶은 경우


똑같이 배포해보면

자식 함수인 getPrice()를 호출하면 프리미엄값인 100이 리턴되고
모함수를 super를 통해 호출하면 모 컨트트의 값 10000이 리턴됨을 볼 수 있다.


Multi Inheritance

꼭 1:1 부모 자식 컨트랙트 상속은 아니다

두 개 이상의 컨트랙트를 상속 받을 수 있다.
예를 들어,

contract CarSeller{
    address private seller;
    string private location;
    
    constructor(address _seller) {
        seller = _seller;
    }

    function getSeller() public view returns(address) {
        return seller;
    }
}

(중략)

contract Benz is Car("SUV", 4, 10000), CarSeller(주소 직접 입력){
    (중략)
}

이런식으로 해당 차의 판매자 정보 또한 포함하고 싶은 경우
' , ' 를 통해 더 상속을 받을 수 있다.


똑같이 배포해보면

잦은 배포로 가스비가 떨어져서
https://cloud.google.com/application/web3/faucet/ethereum/sepolia
이 곳을 통해 Faucet했다.

다시 본론으로

getSeller()도 추가됨을 볼 수 있고 판매자 주소 또한 잘 나오는 것을 볼 수 있다.

이렇게 가중 상속을 통해 더 다양한 방식을 이용할 수 있음을 알 수 있다.


거듭 상속

한 컨트랙트에 여러 컨트랙트를 상속받는 것이 아니라

상속에 상속에 상속같이 거듭해서 상속을 받을 수 있는 방법도 있다.

contract CarSeller{(중략)}

contract Car is CarSeller(주소 직접입력){(중략)}
    
contract Benz is Car("SUV", 4, 10000){(중략)}

전과는 전혀 다른 방식이다.
이것은 상속에 상속인 방식이다.

똑같이 같은 입력값을로 배포해보면

다중 상속과 결과는 같게 나오지만 상황에 따라
달리 사용될 수 있음을 알아한다.

상속을 유연하게 활용해
좋은 컨트랙트를 만드는 것이 중요하다.


시리즈

  1. 값 자료형
  2. 참조 자료형
  3. 블록 & 트랜잭션
  4. Modifier & Error Handling
  5. 현재
  6. 컨트랙트 호출

0개의 댓글