이번 시간에
컨트랙트 상속이다.
두 방식을 잘 이해해 보자
기본적인 상속을 알아보겠다.
예를들어 유명한 독일 자동차 두 회사를 예로 들어보자.
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에서 배포를 하면

두 함수 모두 사용 가능함을 보면 상속이 제대로 됐다는 것을 알 수 있다.
// 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이라고 부른다.
우선 에러가 나는 경우를 보자
// 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다.
function getCarPrice() public view returns(uint) {
return super.getPrice();
}
// 벤츠 컨트랙트 마지막줄에 이 내용을 추가. 모 함수를 사용하고 싶은 경우

똑같이 배포해보면 
자식 함수인 getPrice()를 호출하면 프리미엄값인 100이 리턴되고
모함수를 super를 통해 호출하면 모 컨트트의 값 10000이 리턴됨을 볼 수 있다.
꼭 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){(중략)}
전과는 전혀 다른 방식이다.
이것은 상속에 상속인 방식이다.
똑같이 같은 입력값을로 배포해보면

다중 상속과 결과는 같게 나오지만 상황에 따라
달리 사용될 수 있음을 알아한다.
상속을 유연하게 활용해
좋은 컨트랙트를 만드는 것이 중요하다.