본문 바로가기
블록체인교육/솔리디티

[솔리디티] 12. 함수선택자(function selector), 다른 컨트랙트 사용, 생성

by Danny_Kim 2022. 6. 14.

[NEW] 누구나 쉽게 따라하는 솔리디티 강의(솔리디티 버전 0.8.13)

1. Helloworld, 카운터컨트랙트, 데이터타입

2. 변수, 상수, 불변, 상태변수 읽고 쓰기

3. 이더 단위, 가스와 가스가격

4. 조건문, 반복문, 맵핑(mapping)

5. 배열, 열거형(enum), 구조체(calldata,memory) 

6. 데이터 저장공간, 함수(view,pure 속성)

7. 에러(error), 함수수정자(modifier)

8. 이벤트(events), 생성자(constructor), 상속

9. 상속, 섀도잉,super키워드 함수 속성들

10. 인터페이스(interface), payable, 이더전송,받기 관련

11. Fallback, Call, Delegate(솔리디티 업그레이드 기법)

12. 함수 선택자(function selector), 다른 컨트랙트 사용 및 생성기법

13. Try Catch, Import(임포트), Library(라이브러리)

14. ABI 디코드, hash 함수, 서명검증, 가스최적화

 

1. Function Selector (함수 선택자)

 - 함수가 호출될때, calldata(콜데이터)의 첫 4 bytes는 호출할 함수를 지정한다.

 - 이 4 bytes를 함수 선택자(function selector)이라고 부른다.

 - 예제코드에서 call을 사용하여 컨트랙트 주소 addr로  transfer를 실행하는걸 확인할 수 있다.

addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))

 - abi.encodeWithSignature(..)의 첫번째 4 bytes가 function selector(함수 선택자) 이다.

 - 함수 선택자를 미리 계산하고 inline(인라인) 하면 약간의 가스를 절약할 수 있다.

 - 예제)

 

- 먼저 Receiver 컨트랙트 생성후 transfer()함수 실행후, 이벤트 로그를 확인한다.

- 첫 4bytes가 0xa9059cbb 이다.

- FunctionSelector 작성후 실행해본다.

- getSelector에 "transfer(address,uint256)"을 입력하면, 위 예제에서 본 처음 4bytes가 0xa9059cbb로 동일함을 알수 있다.

전체소스

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

contract Receiver{
    event Log(bytes data);

    function transfer(address _to, uint _amout) external{
        emit Log(msg.data);
        // 0xa9059cbb
        // 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
        // 000000000000000000000000000000000000000000000000000000000000000b
    }
}

contract FunctionSelector {
    /*
    "transfer(address,uint256)"
    0xa9059cbb
    "transferFrom(address,address,uint256)"
    0x23b872dd
    */
    function getSelector(string calldata _func) external pure returns (bytes4) {
        return bytes4(keccak256(bytes(_func)));
    }
}

 

2. 다른 컨트랙트를 호출하는 방법

 - 컨트랙트가 다른 컨트랙트를 호출하는데는 2가지 방법이 있음

 - 가장 쉬운 방법은 A.foo(x,y,z)처럼 호출하는 것임

 - 다른 방법은 다른 컨트랙트가 사용하고 있는 저수준(low-level) call 을 사용하는것. (추천하지 않음)

 - 예제)

 - Callee 컨트랙트에서 setX(), setXandSendEhter() 정의

- Caller에서 SetX(), setXFromAddress(), setXandSendEther() 함수 정의 후 사용하기.

- Callee의 컨트랙트 주소를 Callee _callee, address _addr의 형태로 가져올  수 있음

   (불러온 방식에 따라서 함수 내 사용법 조금 상이함)

 - 마지막 Call 함수 사용은 추천하지 않음(setXandSendEther)

전체소스

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

contract Callee {
    uint public x;
    uint public value;

    function setX(uint _x) public returns (uint) {
        x = _x;
        return x;
    }

    function setXandSendEther(uint _x) public payable returns (uint, uint) {
        x = _x;
        value = msg.value;

        return (x, value);
    }
}

contract Caller {
    function setX(Callee _callee, uint _x) public {
        uint x = _callee.setX(_x);
    }

    function setXFromAddress(address _addr, uint _x) public {
        Callee callee = Callee(_addr);
        callee.setX(_x);
    }

    function setXandSendEther(Callee _callee, uint _x) public payable {
        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
    }
}

 

3. 컨트랙트가 다른 컨트랙트 생성하기

 - 컨트랙트는 new 키워드를 사용해서 다른 컨트랙트에 의해서 생성될 수 있음 (버전 0.8.0 이상부터)

 - new 키워드는 create2를 지원함 (salt 옵션 참고)

 - 예제) 

 

 

- Value에 111 wei 이상 입력 후 deploy

- createAccount 에 owner 주소 입력 후 트랜잭션.

- account에서 0으로 생성된 컨트랙트 주소 확인.

- Account 컨트랙트 선택후, At address에 해당 컨트랙트 주소 입력

- 아래와 같이 새롭게 생성된 컨트랙트 확인 가능.

 

전체소스

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

contract Account{
    address public bank;
    address public owner;

    constructor(address _owner) payable{
        bank = msg.sender;
        owner = _owner;
    }
}

contract AccountFactory{
    Account[] public accounts;

    function createAccount(address _owner) external payable{
        Account account = new Account{value: 111}(_owner);
        accounts.push(account);
    }
}

 

 

3.2 과제) 아래 소스 예제를 실행해보고 차이점 이해하기.

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

contract Car {
    address public owner;
    string public model;
    address public carAddr;

    constructor(address _owner, string memory _model) payable {
        owner = _owner;
        model = _model;
        carAddr = address(this);
    }
}

contract CarFactory {
    Car[] public cars;

    function create(address _owner, string memory _model) public {
        Car car = new Car(_owner, _model);
        cars.push(car);
    }

    function createAndSendEther(address _owner, string memory _model) public payable {
        Car car = (new Car){value: msg.value}(_owner, _model);
        cars.push(car);
    }

    function create2(
        address _owner,
        string memory _model,
        bytes32 _salt
    ) public {
        Car car = (new Car){salt: _salt}(_owner, _model);
        cars.push(car);
    }

    function create2AndSendEther(
        address _owner,
        string memory _model,
        bytes32 _salt
    ) public payable {
        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
        cars.push(car);
    }

    function getCar(uint _index)
        public
        view
        returns (
            address owner,
            string memory model,
            address carAddr,
            uint balance
        )
    {
        Car car = cars[_index];

        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
    }
}

 

소스출처 : https://solidity-by-example.org/

반응형

댓글0