동적인 계약 계정 생성을 통한 Separation of Concerns 구현

Yoonjae Yoo
DNEXT
Published in
5 min readJul 1, 2018

CREATE OPCODE를 활용해서 컴파일 타임에 확정되지 않은 계약 계정을 생성하는 방법

솔리디티로 작성된 스마트 계약에서 새로운 계약 계정을 생성할 때는 new 키워드를 사용합니다. 만약 Outer 라는 계약에서 Inner라는 계약 계정을 생성한다면 아래와 같은 코드를 사용하겠죠.

contract Inner {
function foo() public {
// do something
}
}
contract Outer {
Inner inner;
function newInner() {
inner = new Inner();
}
}

이 방법은 완벽해보이지만, 한가지 결점이 있습니다. 그것은 바로 Outer가 컴파일되는 시점에 Inner의 컴파일된 bytecode를 미리 알고 있어야 한다는 것입니다.

상황에 따라서는 동적으로 추가된 bytecode로 새로운 계약 계정을 생성해야할 때도 있습니다. 위의 예제에서 Inner를 인터페이스로 변경해보겠습니다. 그리고 이 Inner 인터페이스를 구현한 InnerImpl을 Outer에서 생성해봅시다. 그러면 코드는 아래와 같이 변경됩니다.

contract Inner {
function foo() public;
}
contract InnerImpl is Inner {
function foo() public {
// do something
}
}
contract Outer {
Inner inner;
function newInner() {
inner = new InnerImpl();
}
}

이렇게 인터페이스 기반으로 계약을 작성하게 되면 보다 유연한 아키텍처를 구성할 수 있는 장점이 있습니다. 반드시 InnerImpl이 아니더라도 Inner 인터페이스를 만족시키는 계약이라면 어떤 것이든 Outerinner에 저장할 수 있습니다. 이는 Separation of Concerns를 따를 수 있도록 해줍니다.

하지만 아직까지 Solidity에 이를 제대로 구현하기에는 큰 제약이 있습니다.

컴파일 타임에 확정되지 않은 계약을 new 키워드로 생성할 수 없다.

라는 것인데, 위의 예제에서 Outer에서 inner 변수에 저장할 수 있는 계약은InnerImpl 뿐입니다. 왜냐하면 Outer를 컴파일한 시점에서 Inner 인터페이스를 만족시키며 bytecode를 알 수 있는 계약은 InnerImpl 뿐이기 때문입니다.

즉, OuterInner, InnerImpl을 컴파일, 배포한 뒤 InnerImpl을 개선한 InnerImpl2계약을 작성한다고 하더라도, Outer에서 이를 인식하지 못하므로 진정한 Separation of Concerns를 구현할 수 없다는 의미입니다.

DNext에서는 이더리움 기반의 탈중앙화된 토큰 런칭 플랫폼인 TokenBoost를 구현할 때 이 문제에 부딪치게 되었습니다. TokenBoost에서는 토큰을 런칭할 때 사용할 수 있는 여러 전략(Strategy)이 존재하고 사용자가 원하는 전략을 선택해서 토큰 판매에 반영할 수 있습니다. (시작/종료 시간, 소프트캡/하드캡 등등이 선택할 수 있는 전략입니다.)

우리는 사용자들이 TokenBoost에서 미리 정의된 전략만 사용할 수 있도록 설계하지 않으려고 했습니다. TokenBoost가 런칭된 이후에도 다양한 전략들이 추가될 수 있고, 이러한 전략들이 거래될 수 있는 마켓플레이스를 만들려고 했습니다.

이를 위해서는 동적으로 계약을 생성할 수 있어야 합니다. 즉, Outer 계약이 배포된 이후에도 Inner를 구현하는 어떤 스마트 계약이라도 inner 변수에 저장할 수 있어야 한다는 의미입니다.

이를 어떻게 해결할 수 있을까요? 방법은 EVM opcode를 사용하는 것입니다. Opcode중 create(v, p, s)라는 것이 있습니다. 메모리의 p 번지에서 s 번지까지의 bytecode를 이용해 v 만큼의 wei 값을 갖는 계약 계정을 생성하는 명령어입니다.

이를 사용하게 되면 아래와 같이 동적으로 계약 계정을 생성할 수 있습니다.

contract Inner {
function foo() public;
}
contract Outer {
Inner inner;
function newInner(bytes _code) {
address addr;
assembly {
addr := create(0, add(_code, 0x20), mload(_code))
}
inner = Inner(addr);
}
}

_code로는 Inner 인터페이스를 구현한 계약의 bytecode를 입력해주면 됩니다. 그러면 create opcode가 해당 bytecode로 새로운 계약을 생성한 후 그 주소값을 addr에 저장합니다.

이렇게 코드를 작성하면, Outer가 컴파일, 배포된 이후에 생성된 Inner 인터페이스를 만족하는 계약을 배포하고 해당하는 계약 계정을 생성할 수 있습니다. 업그레이드 가능한 스마트 계약을 구현하는 데에도 도움이 될 것입니다. 혹시나 초기에 배포한 Inner 구현 계약에 버그가 있다면 새로운 구현 계약을 컴파일해서 newInner() 함수를 호출해주면 되고, 이는 Inner 로직이 업그레이드되는 효과를 가져옵니다.

블록체인 스타트업 DNext에서는 탈중앙화된 표준 토큰 런칭 플랫폼인 “TokenBoost” 서비스를 오픈할 예정입니다.

▶TokenBoost 프라이빗 베타 신청하기: https://tokenboost.net/

--

--