[Solidity] Creating Contracts from a Contract, Salt, Try Catch

jhcha·2023년 8월 4일
0

Solidity

목록 보기
10/17
post-thumbnail

Creating Contracts from a Contract

url: https://solidity-by-example.org/new-contract/

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

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);
    }
}

new 키워드를 사용하여 다른 컨트랙트를 생성할 수 있다. 0.8.0 버전 이후 new 키워드는 salt 옵션을 지정하는 create2 기능을 지원한다.

// create를 통해 생성한 컨트랙트 주소는 결정론적 결과 값이 나온다.
CarFactory.create(...)
// salt 값이 동일하고, 함수의 인자가 동일하면 동일한 주소의 컨트랙트가 생성된다. 
// 동일한 주소의 컨트랙트 생성 불가, 만약 salt값이 동일하지만 함수의 인자가 다르면 컨트랙트 주소도 변경된다.
CarFactory.create2(...)

// create와 create2 차이는 위의 예시와 같고 단지 이더리움을 송신하는 payable 여부만 차이를 가진다.
CarFactory.createAndSendEther(...)
CarFactory.create2AndSendEther(...)

salt 옵션 지정을 통해 생성될 컨트랙트 주소를 예측할 수 있다.

Salt

컨트랙트 생성 시 컨트랙트의 주소는 nonce라는 것을 이용하여 단방향 함수처럼 예측할 수 없는 결정론적 값으로 생성된다고 한다. 그래서, 컨트랙트 주소를 예측 가능 하도록 salt를 사용하는 것이 create2 기능 이라고 하는 것 같다.

contract D {
    uint public x;
    constructor(uint a) {
        x = a;
    }
}

contract C {
    event Log(string name, address indexed add, uint arg);
    function createDSalted(bytes32 salt, uint arg) public {
        // This complicated expression just tells you how the address
        // can be pre-computed. It is just there for illustration.
        // You actually only need ``new D{salt: salt}(arg)``.
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(D).creationCode,
                abi.encode(arg)
            ))
        )))));
        D d = new D{salt: salt}(arg);
        emit Log("predictedAddress", predictedAddress, arg);
        emit Log("D address", address(d), arg);
        require(address(d) == predictedAddress);
    }
}

해당 코드는 solidity 공식 문서에 있는 예제이다. 해당 예제는 salt를 사용하여 예측가능한 주소 값을 통해 생성된 컨트랙트의 주소와 같은지 비교한다.
32 바이트의 salt 값을 입력한 경우, 다음과 같이 사전 계산을 통해 새로 생성될 D 컨트랙트의 주소와 일치하는 것을 확인할 수 있다.

추가적으로 solidity 공식 문서에서는 다음과 같이 설명한다.
"컨트랙트 주소는 작성하는 컨트랙트 주소와 컨트랙트를 작성할 때 마다 증가하는 카운터로 계산된다. salt 옵션 (32bytes)을 지정하면 다른 메커니즘으로 새 컨트랙트 주소로 생성된다. 생성 컨트랙트 주소, salt 값, 생성된 컨트랙트의 바이트 코드 및 생성자 인수로 주소를 계산한다. 이 때, 카운터 ("nonce")는 사용되지 않는다."

참고자료: https://seungddak.tistory.com/229
참고자료: https://docs.soliditylang.org/en/v0.8.18/control-structures.html#creating-contracts-via-new

Try Catch

url: https://solidity-by-example.org/try-catch/

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

// External contract used for try / catch examples
contract Foo {
    address public owner;

    constructor(address _owner) {
        require(_owner != address(0), "invalid address");
        assert(_owner != 0x0000000000000000000000000000000000000001);
        owner = _owner;
    }

    function myFunc(uint x) public pure returns (string memory) {
        require(x != 0, "require failed");
        return "my func was called";
    }
}

contract Bar {
    event Log(string message);
    event LogBytes(bytes data);

    Foo public foo;

    constructor() {
        // This Foo contract is used for example of try catch with external call
        foo = new Foo(msg.sender);
    }

    // Example of try / catch with external call
    // tryCatchExternalCall(0) => Log("external call failed")
    // tryCatchExternalCall(1) => Log("my func was called")
    function tryCatchExternalCall(uint _i) public {
        try foo.myFunc(_i) returns (string memory result) {
            emit Log(result);
        } catch {+*++
            emit Log("external call failed");
        }
    }

    // Example of try / catch with contract creation
    // tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")
    // tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")
    // tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")
    function tryCatchNewContract(address _owner) public {
        try new Foo(_owner) returns (Foo foo) {
            // you can use variable foo here
            emit Log("Foo created");
        } catch Error(string memory reason) {
            // catch failing revert() and require()
            emit Log(reason);
        } catch (bytes memory reason) {
            // catch failing assert()
            emit LogBytes(reason);
        }
    }
}

Try / Catch는 외부 함수 호출이나 컨트랙트 생성에서 발생하는 오류에 대해서만 기능한다.

Java에서 사용하는 Try / Catch와 기능적으로 비슷하지만 사용 범위가 문법에 의해 제한되어 있는 형태처럼 보인다.

// 외부 함수를 호출하는 경우
try foo.myFunc(_i) returns (string memory result) { catch {...} }

// 컨트랙트를 생성하는 경우
try new Foo(_owner) returns (Foo foo) { 
	catch Error(string memory reason) {}
	catch (bytes memory reason) {}
}

1개의 댓글

comment-user-thumbnail
2023년 8월 4일

많은 것을 배웠습니다, 감사합니다.

답글 달기