Solidity 패턴 분석 - Access Restriction
Access Restriction
Intent
적절한 기준에 따라 컨트랙트 기능에 대한 액세스를 제한합니다.
Motivation
블록체인의 공개적 특성으로 인해 컨트랙트에 대한 완전한 프라이버시를 보장할 수 없습니다. 모든 컨트랙트가 공개되기 때문에 다른 사람이 블록체인에서 컨트랙트 상태를 읽는 것을 막을 수 없습니다. 할 수 있는 일은 다른 컨트랙트에 의해 컨트랙트 상태에 대한 읽기 액세스를 제한하는 것입니다. 이는 상태 변수를 private 으로 선언하면 됩니다. 또한 함수를 private 으로 선언할 수 있지만 그렇게 하면 컨트랙트 범위를 벗어난 모든 사람이 어떤 상황에서도 함수를 호출할수 없게 됩니다. 그러나 public으로 선언하면 네트워크의 모든 참가자에게 액세스 권한이 주어집니다. 대부분의 경우 특정 규칙이 충족되는 경우 함수에 대한 액세스를 허용하는 것이 좋습니다. 종종 액세스는 컨트랙트 관리자와 같이 정의된 엔터티 집합으로 제한되어야 합니다. 다른 제한 사항은 특정 시점 또는 액세스 엔터티가 액세스 비용을 지불할 용의가 있는 경우에만 허용해야 합니다. 이러한 모든 제한 사항은 Access Restriction 패턴을 적용하여 실현할 수 있으므로 스마트 컨트랙트 기능에 대한 무단 액세스에 대한 보안을 부여합니다.
Applicability
다음과 같은 경우 Access Restriction 패턴을 사용합니다.
- 컨트랙트 함수는 특정 상황에서만 호출할 수 있어야 합니다.
- 여러 함수에 유사한 제한을 적용하려고 합니다.
- 무단 액세스에 대한 스마트 컨트랙트의 보안을 높이고 싶습니다.
Participants & Collaborations
이 패턴의 참여자는 제한할 함수의 호출 엔터티와 함수가 속한 계약입니다. 호출 엔터티는 사용자 또는 다른 계약일 수 있으며 각 계약 주소로 트랜잭션을 전송하여 기능을 호출합니다. 호출된 계약에 관련된 행위자는 제한된 기능과 실제 액세스 제어를 담당하는 추가 구성 요소입니다.
Implementation
액세스 제한 패턴의 구현을 위해 우리는 Guard Check 패턴 을 사용하고 있습니다. Guard Check 패턴이 제공하는 기능을 사용하면 함수가 호출되면 필요한 상황을 확인하고 충족되지 않는 경우 예외 처리 할 수 있습니다. 해당 함수의 시작 부분에도 가드 체크를 배치할 수 있습니다. 그러나 이러한 가드 체크는 둘 이상의 함수에 대해 재사용되는 경우가 많으므로 작업을 함수 제어자에게 아웃소싱한 다음 이를 필요로 하는 함수에 적용할 것을 권장합니다. 함수 제어자는 각 함수의 입력 매개변수에서 인수를 가져오거나, 자체 인수를 제공되거나, 본문에 조건을 하드 코딩하여 재사용을 제한할 수 있습니다.
이러한 함수 제어자의 구조는 일반적으로 동일한 패턴을 따릅니다. 처음에는 필요한 조건을 검사합니다. 그 후 처음 함수로 되돌아갑니다. 이 동작은 함수 제어자 코드에서 밑줄(_;)로 표시됩니다. 함수 다음에 실행해야 하는 추가 코드가 있는 경우 함수 제어자에서 밑줄(_;) 다음에 추가 코드를 삽입할 수 있습니다. 이 패턴, 특히 함수 제어자의 시작 부분에 있는 조건 검사는 다양한 액세스 제한을 제공하기 위해 여러 방식으로 적용될 수 있습니다.
Sample Code
다음 코드는 소유자가 소유한 예제 컨트랙트에서 세 가지 종류의 액세스 제한을 사용하며 Solidity 문서의 예제에 영향을 받습니다. 소유권은 현재 소유자가 직접 양도하거나 소유권의 마지막 변경 이후 1개월 경과 후에 1 이더리움으로 아무나 구입할 수 있습니다.
// This code has not been professionally audited, therefore I cannot make any promises about
// safety or correctness. Use at own risk.
pragma solidity ^0.4.21;
contract AccessRestriction {
address public owner = msg.sender;
uint public lastOwnerChange = now;
modifier onlyBy(address _account) {
require(msg.sender == _account);
_;
}
modifier onlyAfter(uint _time) {
require(now >= _time);
_;
}
modifier costs(uint _amount) {
require(msg.value >= _amount);
_;
if (msg.value > _amount) {
msg.sender.transfer(msg.value - _amount);
}
}
function changeOwner(address _newOwner) public onlyBy(owner) {
owner = _newOwner;
}
function buyContract() public payable onlyAfter(lastOwnerChange + 4 weeks) costs(1 ether) {
owner = msg.sender;
lastOwnerChange = now;
}
}
5-6행에서 상태 변수 owner 및 lastOwnerChange는 컨트랙트 작성자(owner) 및 컨트랙트 작성 시간(lastOwnerChange)에 초기화됩니다.. 8행의 함수제어자 onlyBy(address_account)는 26행의 changeOwner(...) 함수에 연결되고 함수의 이니시에이터(msg.sender)가 함수제어자 호출에 제공된 변수가 소유자와 동일해야 합니다. 이 함수제어자를 사용하면 현재 소유자가 아닌 다른 사용자가 함수제어자로 보호되고 있는 함수를 호출할 때마다 예외 처리됩니다.
두 번째 함수제어자 onlyAfter(uint_time)는 첫번째 함수제어자와 같은 방식으로 작동합니다. 지정된 시간전에 함수가 호출될 경우 예외 처리하는 점이 차이점입니다. 30행에서 사용되며 소유권의 마지막 변경 시간과 4주(Solidity는 시간 단위로 월(month)을 지원하지 않기 때문에 1개월 대신 4주가 추가됩니다.)가 함께 제공됩니다. 따라서 함수 호출은 마지막 소유권 변경 후 최소 4주가 경과한 후에만 성공할 수 있습니다.
18행의 세 번째이자 마지막 함수제어자 costs(unint_mount)은 통화량(금액)을 입력으로 하고, 보호 함수의 실행에 들어가기 전에 호출 트랜잭션과 함께 제공되는 값이 지정된 금액과 비교합니다. 이 함수제어자는 20행의 밑줄에 의해 트리거되는 함수 실행 후에 구현된 코드를 가지고 있기 때문에 다른 두 함수제어자와 다릅니다. 21행에서 시작하는 추가 if 절은 거래에 필요한 양보다 많은 돈이 제공되었는지 확인하고 거래 금액을 제외한 잉여금을 보낸 사람에게 다시 송금합니다. 이것은 함수제어자가 제공할 수 있는 다양한 가능성을 보여주는 좋은 예입니다. 이전 함수제어자와 결합하여 마지막 소유권 변경 후 4주가 지난 후, 트랜잭션에 1이더리움이 포함된 경우에만 새 소유자가 컨트랙트를 구입할 수 있습니다. 이 거래로 돈을 받을 수 있으려면 30행의 buyContract() 함수의 payable 함수제어자가 필요합니다.
이 간단한 예에서, 우리는 기능성, 복잡성을 잃지 않고 함수제어자를 해당 함수 본문에 직접 코드를 구현할 수 있었습니다.
두 개 이상의 함수가 동일하거나 유사한 요구 사항(State Machine pattern)을 공유하면 함수 제어자를 통해 쉽게 재사용 할수 있으므로 함수를 함수제어자로 아웃소싱하는 이점이 명백해집니다.
Consequences
액세스 제한 패턴을 적용할 때 몇 가지 결과를 고려해야 합니다. 논란의 여지가 있는 점 중 하나는 코드의 가독성입니다. 한편으로, 특히 제공된 샘플 코드에서와 같이 함수 제어자에 의미 있는 이름이 지정된 경우 제한 기준이 함수 헤더에서 명확하게 인식될 수 있기 때문에 함수 제어자를 사용하면 코드를 더 쉽게 이해할 수 있습니다. 반면에 실행 흐름은 코드의 한 줄에서 완전히 다른 줄로 건너뛰기 때문에 코드를 따르고 감사하기가 더 어려워지고, 따라서 잘못된(악성) 코드를 도입되는 것이 단순해집니다. 이러한 이유로 새로운 스마트 계약 프로그래밍 언어 Vyper 는 함수제어자(modifiers)를 포기하고 있습니다.
패턴의 장점은 다양한 상황에 쉽게 적응할 수 있고 재사용 가능성이 높으면서도 기능에 대한 액세스를 제한하는 안전한 방법을 제공하여 스마트 계약 보안을 모두 향상시킬 수 있다는 사실에서 나옵니다.
Known Uses
이 패턴의 가장 유명한 예는 아마도 OpenZeppelin의 Ownerable 계약일 것 입니다.
또 다른 예는 CrytoKitties DApp의 코어 컨트랙트로 소유자가 한 명뿐 아니라 세 명입니다. 즉 CEO, CFO 및 COO는 서로 다른 보안 수준을 가지고 있으며, 액세스할 수 있는 기능도 다릅니다.