> For the complete documentation index, see [llms.txt](https://funarchy.gitbook.io/funarchy/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://funarchy.gitbook.io/funarchy/introduction/analyzed-target/polymarket.md).

# Polymarket

## **Configuration**

Polymarket은 각 마켓을 온체인에서 고유하게 식별하기 위해 Gnosis CTF의 `conditionId`를 기본 키(Primary Key)로 사용합니다.

### Create Market Process

#### 1. 시장 초기화 및 질문 등록 (`UmaCtfAdapter.sol`)

{% code title="UmaCtfAdapter.sol" expandable="true" %}

```solidity
    function initialize(
        bytes memory ancillaryData,
        address rewardToken,
        uint256 reward,
        uint256 proposalBond,
        uint256 liveness
    ) external returns (bytes32 questionID) {
        if (!collateralWhitelist.isOnWhitelist(rewardToken)) revert UnsupportedToken();

        bytes memory data = AncillaryDataLib._appendAncillaryData(msg.sender, ancillaryData);
        if (ancillaryData.length == 0 || data.length > MAX_ANCILLARY_DATA) revert InvalidAncillaryData();

        questionID = keccak256(data);

        if (_isInitialized(questions[questionID])) revert Initialized();

        uint256 timestamp = block.timestamp;

        // Persist the question parameters in storage
        _saveQuestion(msg.sender, questionID, data, timestamp, rewardToken, reward, proposalBond, liveness);

        // Prepare the question on the CTF
        ctf.prepareCondition(address(this), questionID, 2);

        // Request a price for the question from the OO
        _requestPrice(msg.sender, timestamp, data, rewardToken, reward, proposalBond, liveness);

        emit QuestionInitialized(questionID, timestamp, msg.sender, data, rewardToken, reward, proposalBond);
    }
```

{% endcode %}

마켓 생성은 `UmaCtfAdapter.sol`의 `initialize()` 함수 호출에서 시작됩니다. 이 단계에서 자연어 규칙이 온체인 ID로 변환되는 첫 번째 과정이 수행됩니다.

* 동작: UI로 보는 실제 자연어 데이터인 `ancillaryData`와 생성자 주소를 결합하여 `questionID`를 생성합니다.
* CTF 연결: 생성된 `questionID`를 바탕으로 `ctf.prepareCondition`을 호출하여 CTF 컨트랙트에 시장 생성을 요청합니다.

#### 2. `prepareCondition()` 함수에서 `conditionId`를 생성

<pre class="language-solidity" data-title="" data-expandable="true"><code class="lang-solidity">    // Gnosis CTF
    function prepareCondition(address oracle, bytes32 questionId, uint outcomeSlotCount) external {
        ...
        bytes32 <a data-footnote-ref href="#user-content-fn-1">conditionId</a> = CTHelpers.getConditionId(oracle, questionId, outcomeSlotCount);
        require(payoutNumerators[conditionId].length == 0, "condition already prepared");
        payoutNumerators[conditionId] = new uint[](outcomeSlotCount);
        emit ConditionPreparation(conditionId, oracle, questionId, outcomeSlotCount);
    }  
    // CTHelpers
    function getConditionId(address oracle, bytes32 questionId, uint outcomeSlotCount) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount));
    }
</code></pre>

Gnosis CTF에서 시장을 식별하는 고유 키인 `conditionId`는 세 가지 파라미터의 결합으로 생성된다.

**식별자 생성 공식:**

$$
conditionId = keccak256(abi.encodePacked(oracle,\ questionId,\ outcomeSlotCount))
$$

**변수 설명**

* Oracle Address (`address(this)`)
  * 코드 흐름: `UmaCtfAdapter.initialize` 함수에서 `ctf.prepareCondition(address(this), ...)`를 호출한다.
  * Polymarket에서는 UMA 오라클과 통신하는 Adapter 컨트랙트 자체 주소가 오라클로 등록된다.
* Question ID (`bytes32 questionID`)
  * 코드 흐름: `questionID = keccak256(data)`를 통해 생성됩니다. 여기서 `data`는 UMA 오라클이 참조할 자연어 규칙(`ancillaryData`)과 호출자 정보가 결합된 데이터입니다.
  * 구체적인 질문 내용과 시장의 규칙을 식별한다. 자연어 규칙에서 단 한 글자만 달라져도 해시값이 완전히 변하므로, 서로 다른 질문을 가진 시장들이 온체인상에서 충돌 없이 분리된다.
* Outcome Slot Count (`uint 2`)
  * 코드 흐름: 폴리마켓의 `initialize` 함수는 `outcomeSlotCount`를 `2`로 고정하여 전달합니다.
  * 시장이 가질 수 있는 결과의 가짓수를 정의합니다. Binary 시장(Yes/No)의 경우 항상 2개(0번 슬롯, 1번 슬롯)의 결과값을 가짐을 명시합니다.

#### 3. 상태 저장 및 관리 (`payoutNumerators`)

생성된 `conditionId`는 단순히 식별자로만 쓰이는 것이 아니라, 해당 시장의 결괏값을 저장하는 스토리지 키로 사용됩니다.

```solidity
payoutNumerators[conditionId] = new uint[](outcomeSlotCount);
```

* 초기화: `payoutNumerators` 매핑에 해당 `conditionId`를 키로 하는 `uint` 배열을 할당합니다.
* 중복 방지: 이미 존재하는 `conditionId`일 경우 `require` 문에 의해 트랜잭션이 되돌려지므로, 동일한 조건의 시장이 중복 생성되는 것을 차단합니다.
* 정산 대기: 이 배열은 시장이 종료된 후 오라클에 의해 실제 결과값(예: \[1, 0] 또는 \[0, 1])이 기록될 공간이 됩니다.

#### 4. UMA 오라클 요청 및 Lifecycle 설정 (`_requestPrice`)

`initialize` 함수의 마지막 단계인 `_requestPrice`는 생성된 질문 데이터를 UMA Optimistic Oracle(OO)에 전달하고, 마켓의 보안 및 시간 파라미터를 확정합니다.

{% code expandable="true" %}

```solidity
    function _requestPrice(
        address requestor,
        uint256 requestTimestamp,
        bytes memory ancillaryData,
        address rewardToken,
        uint256 reward,
        uint256 bond,
        uint256 liveness
    ) internal {
        if (reward > 0) {
            // If the requestor is not the Adapter, the requestor pays for the price request
            // If not, the Adapter pays for the price request
            if (requestor != address(this)) {
                TransferHelper._transferFromERC20(rewardToken, requestor, address(this), reward);
            }

            // Approve the OO as spender on the reward token from the Adapter
            if (IERC20(rewardToken).allowance(address(this), address(optimisticOracle)) < reward) {
                IERC20(rewardToken).approve(address(optimisticOracle), type(uint256).max);
            }
        }

        // Send a price request to the Optimistic oracle
        optimisticOracle.requestPrice(
            YES_OR_NO_IDENTIFIER, requestTimestamp, ancillaryData, IERC20(rewardToken), reward
        );

        // Ensure the price request is event based
        optimisticOracle.setEventBased(YES_OR_NO_IDENTIFIER, requestTimestamp, ancillaryData);

        // Ensure that the dispute callback flag is set
        optimisticOracle.setCallbacks(
            YES_OR_NO_IDENTIFIER,
            requestTimestamp,
            ancillaryData,
            false, // DO NOT set callback on priceProposed
            true, // DO set callback on priceDisputed
            false // DO NOT set callback on priceSettled
        );

        // Update the proposal bond on the Optimistic oracle if necessary
        if (bond > 0) optimisticOracle.setBond(YES_OR_NO_IDENTIFIER, requestTimestamp, ancillaryData, bond);
        if (liveness > 0) {
            optimisticOracle.setCustomLiveness(YES_OR_NO_IDENTIFIER, requestTimestamp, ancillaryData, liveness);
        }
    }

```

{% endcode %}

**핵심 로직 분석**

* 오라클에 결과 요청 (`requestPrice`):
  * `YES_OR_NO_IDENTIFIER`: UMA 내부에서 이 시장이 Yes/No(Binary) 시장임을 식별하는 상수를 사용합니다.
  * `ancillaryData`: 앞서 생성자 정보가 결합된 자연어 규칙 데이터가 전달되어, 오라클 판정의 근거가 됩니다.
* 콜백 설정 (`setCallbacks`):
  * `priceDisputed` 플래그를 `true`로 설정합니다. 이는 오라클에 제출된 결과에 분쟁이 발생했을 때, Adapter 컨트랙트가 이를 즉시 인지하여 온체인 상태를 업데이트(예: 마켓 일시 중지)할 수 있게 합니다.
* 경제적 보안 강제 (`setBond` & `setCustomLiveness`):

  * Bond (담보금): 결과 제안자가 잘못된 값을 제출할 경우 몰수되는 금액입니다. 시장의 예치금이 클수록 더 높은 Bond를 설정하여 공격 비용을 높입니다.
  * Liveness (대기 시간): 결과가 제안된 후 최종 확정되기까지의 이의 제기 가능 시간입니다. 이 기간이 지나야만 거래 결과가 확정됩니다.

아래 링크는 실제 polymarket에서 initialize하는 트랜잭션입니다.

{% embed url="<https://dashboard.tenderly.co/tx/0xb5a5df94fa21c2a09a4e880df7a60d6ab7414807e83a7122e010d401218c71d8?trace=0>" %}

***

### Market 활성화 및 토큰 등록 (`registerToken`)

Gnosis CTF를 통해 `conditionId`가 생성된 것만으로는 거래가 불가능합니다. `CTFExchange` 컨트랙트가 해당 토큰을 거래 가능한 자산으로 인식하도록 등록하는 과정이 선행되어야 합니다. 이는 관리자(`onlyAdmin`)가 수행하는 인프라 설정 단계입니다.

**1) `registerToken` 함수 분석**

거래소 컨트랙트의 `Registry` 믹스인을 통해 특정 토큰의 상보적 관계와 소속 마켓을 정의합니다.

Solidity

```solidity
/// @notice 거래소에서 거래될 tokenId, 그 보완 토큰(complement) 및 conditionId를 등록
/// @param token        - 등록할 토큰의 ID (예: YES)
/// @param complement   - 해당 토큰의 상보적 토큰 ID (예: NO)
/// @param conditionId  - 해당 토큰이 속한 CTF 마켓 식별자
function registerToken(uint256 token, uint256 complement, bytes32 conditionId) external onlyAdmin {
    _registerToken(token, complement, conditionId);
}
```

**2) 등록 파라미터의 역할**

* token & complement: Polymarket의 핵심 로직인 Mint/Merge를 실행하기 위해 필수적인 정보입니다. 'Yes' 주문이 들어왔을 때 'No' 주문과 매칭하여 자산을 발행하려면, 컨트랙트는 특정 토큰 ID의 짝이 되는 토큰이 무엇인지 온체인 레지스트리에서 즉시 조회할 수 있어야 합니다.
* conditionId: 해당 토큰이 어떤 오라클 규칙과 결합되어 있는지 연결합니다. 이는 추후 시장이 종료되었을 때 정산(Redemption)을 처리하는 근거 데이터가 됩니다.

**3) 마켓 배포 파이프라인에서의 위치**

트랜잭션 분석 결과 `initialize`와 `registerToken` 사이의 시차가 발생하는 이유는 다음과 같은 운영 단계 때문입니다.

1. Condition 생성 (`initialize`): 마켓의 주제와 규칙을 온체인에 선포 (오라클 레이어).
2. 내부 준비: 서버 오더북 세팅, UI 업데이트, 초기 유동성 확보를 위한 마켓 메이커 협의.
3. 거래소 상장 (`registerToken`): 모든 준비가 끝난 후 `CTFExchange`에 토큰을 등록하여 오퍼레이터의 매칭 실행을 허용 (애플리케이션 레이어).

### Liquidity Provision

`registerToken`를 통해 마켓 등록이 완료되더라도, 오더북에 매도 호가(Ask)가 존재하지 않으면 일반 사용자는 자산을 구매할 수 없습니다. 따라서 마켓 메이커(MM)는 시장 초기에 직접 담보 자산(USDC)을 Gnosis CTF 컨트랙트에 예치하고 조건부 토큰을 발행받아 유동성을 공급해야 합니다. 이 과정은 `splitPosition` 함수를 통해 온체인에서 수행됩니다.

#### **1) `splitPosition` 호출 및 초기 유동성 확보**

마켓 메이커는 초기 유동성을 공급하기 위해 대량의 USDC를 담보로 제공하고, 가능한 모든 결과 토큰 세트를 발행받습니다.

* 실행: MM이 10,000 USDC를 Gnosis CTF의 `splitPosition` 함수 파라미터로 전송합니다.
* 결과: 컨트랙트는 10,000 YES 토큰과 10,000 NO 토큰을 MM의 지갑으로 발행(Mint)합니다.
* 호가 제출: MM은 발행받은 토큰을 오프체인 오더북 엔진에 분할하여 매도 호가로 배치합니다. 일반 사용자는 이후 하이브리드 CLOB(`CTFExchange.matchOrders`)를 통해 이 토큰들을 구매하게 됩니다.

#### 2) `splitPosition` 내부 무결성 검증 (비트마스크 연산)

Gnosis CTF는 자산의 무단 초과 발행을 막기 위해, 요청된 분할 배열(Partition)이 전체 확률 공간(100%)을 중복 없이 완벽히 덮는지 수학적으로 검증합니다. 가스비 최적화를 위해 무거운 상태 변수나 복잡한 루프 대신 Low-level 비트 연산(`&`, `^`)을 사용합니다.

Solidity

```solidity
// Gnosis CTF: splitPosition 내부의 파티션 무결성 검증 로직
// 예: 2개의 결과(Yes/No)를 가진 마켓의 경우 outcomeSlotCount = 2

uint fullIndexSet = (1 << outcomeSlotCount) - 1; // 1을 2번 시프트 후 1차감 -> 0b11 (3)
uint freeIndexSet = fullIndexSet;                // 남은 공간을 전체 공간(0b11)으로 초기화

// 제출된 partition 배열(예: [1, 2], 즉 [0b01, 0b10])을 순회하며 검증
for (uint i = 0; i < partition.length; i++) {
    uint indexSet = partition[i];
    
    // 1. 유효성 검증: 제출된 조각(indexSet)이 유효한 범위(0초과, 전체 미만) 내에 있는지 확인
    require(indexSet > 0 && indexSet < fullIndexSet, "got invalid index set");
    
    // 2. 서로소(Disjoint) 및 중복 검증: 논리곱(AND) 연산
    // (indexSet & freeIndexSet) 결과가 indexSet과 동일해야 중복 없는 분할임
    require((indexSet & freeIndexSet) == indexSet, "partition not disjoint");
    
    // 3. 공간 차감: 배타적 논리합(XOR) 연산으로 남은 공간에서 현재 조각 제거
    freeIndexSet ^= indexSet;
    
    // (Position ID 계산 및 수량 할당 로직 생략)
}

// 4. 완전성 검증: 모든 조각을 뺐을 때 남은 공간이 0이어야 함
// 0이 아니라면 partition이 전체 결과를 대변하지 못하는 불완전한 상태임
if (freeIndexSet == 0) {
    // 완전한 분할로 판명될 경우, 담보 자산을 컨트랙트로 이전하고 토큰(_batchMint) 발행 실행
}
```

**주요 변수 및 검증 단계 기술 분석**

* 비트 플래그 매핑 (`partition`): Gnosis CTF는 마켓의 결과(Outcome Slot)를 이진수 비트 위치에 1대1로 매핑합니다. 바이너리 마켓(Yes/No)에서 사용자가 `[1, 2]`를 전달하면, 이는 십진수 1(`0b01`, YES 슬롯)과 십진수 2(`0b10`, NO 슬롯)로 해석되어 각각의 토큰 조각을 식별합니다.
* 전체 공간 정의 (`fullIndexSet`): `(1 << outcomeSlotCount) - 1` 공식을 통해 해당 마켓에서 발생 가능한 모든 결과를 1로 채운 기준값을 도출합니다. 결과가 2개면 `0b11`(십진수 3), 3개면 `0b111`(십진수 7)이 되어 100% 확률 공간에 대한 정답지를 만듭니다.
* 교집합 및 중복 검증 (`&` 연산): `require((indexSet & freeIndexSet) == indexSet)`는 현재 처리 중인 조각이 남은 공간에 온전히 속하는지 확인합니다. 만약 악의적 사용자가 `[1, 1]`을 요청해 자산을 중복 발행하려 하면, 두 번째 루프에서 논리곱 결과가 달라져 트랜잭션이 즉시 Revert 됩니다.
* 공간 차감 (`^` 연산): 교집합 검증을 통과하면 배타적 논리합(`freeIndexSet ^= indexSet`)을 통해 남은 공간에서 해당 비트를 `0`으로 토글(제거)합니다.
* 완전성 검증 (`freeIndexSet == 0`): 루프 종료 후 남은 비트가 `0`이라는 것은, 제출된 조각들이 빈틈이나 중첩 없이 전체 확률 공간을 100% 완벽하게 분할했음을 온체인에서 수학적으로 증명합니다.

***

#### 3) 비트 연산 검증의 기술적 이점

* 자산 가치 강제: 단 한 줄의 조건문(`require((indexSet & freeIndexSet) == indexSet)`)으로 이중 지불 및 초과 발행 시도를 원천 차단합니다. 이를 통해 스마트 컨트랙트 레벨에서 항상 $$1\ USDC = 1\ Yes + 1\ No$$의 경제적 가치 등가가 보장됩니다.
* 다중 선택지(Categorical) 확장성: 결과가 3개 이상인 마켓에서도 로직의 변경이 일절 필요 없습니다. `fullIndexSet` 변수의 비트 길이만 $$2^N-1$$ 형태로 확장(예: 3지선다일 경우 `0b111`)하여 동일한 로우레벨 비트 연산으로 무결성을 검증할 수 있습니다.

### Lifecycle 파라미터 설정

* 시장의 시작부터 종료, 그리고 결과 확정 대기 시간(`Liveness Period`)이 컨트랙트에 어떻게 기록되는지 분석
* `requestTimestamp`와 `expiration` 값이 시장의 거래 가능 기간을 어떻게 강제하는지 설명

#### Negrisk

시장하나만 YES

다중 YES 시장?

kalshi 같은 마켓에 대해서 다른 대응한? 다른 마켓구성 사건

YES 여러개 시장? UMACtfAdapter에서 마켓 여러개 만들기?

## **Trading**

### Token Mint & Merge

Polymarket의 거래 엔진은 단순히 자산을 맞바꾸는(Swap) 기능을 넘어, 시장 상황에 따라 온체인 자산을 직접 발행(Mint)하거나 소멸(Merge) 과정을 수행합니다. 이 과정은 `_executeMatchCall` 함수를 통해 결정론적으로 실행됩니다.

#### 1. `_executeMatchCall` 함수 분석

거래 엔진은 매수/매도 주문의 조합을 분석하여 `MatchType`을 결정하고, 그에 따라 Gnosis CTF 컨트랙트와 상호작용합니다.

{% code expandable="true" %}

```solidity
/// @notice Executes a CTF call to match orders by minting new Outcome tokens
/// or merging Outcome tokens into collateral.
/// @param makingAmount - Amount to be filled in terms of maker amount
/// @param takingAmount - Amount to be filled in terms of taker amount
/// @param makerAssetId - The Token Id of the Asset to be sold
/// @param takerAssetId - The Token Id of the Asset to be received
/// @param matchType    - The match type
function _executeMatchCall(
    uint256 makingAmount,
    uint256 takingAmount,
    uint256 makerAssetId,
    uint256 takerAssetId,
    MatchType matchType
) internal {
    if (matchType == MatchType.COMPLEMENTARY) {
        // Indicates a buy vs sell order
        // no match action needed
        return;
    }
    if (matchType == MatchType.MINT) {
        // Indicates matching 2 buy orders
        // Mint new Outcome tokens using Exchange collateral balance and fill buys
        return _mint(getConditionId(takerAssetId), takingAmount);
    }
    if (matchType == MatchType.MERGE) {
        // Indicates matching 2 sell orders
        // Merge the Exchange Outcome token balance into collateral and fill sells
        return _merge(getConditionId(makerAssetId), makingAmount);
    }
}
```

{% endcode %}

#### 2. MatchType에 따른 자산 흐름 메커니즘

**MINT (상보적 발행)**

* 상태: 사용자 A("Yes" 매수)와 사용자 B("No" 매수)의 주문이 매칭됨.
* 기술적 동작: 오퍼레이터는 두 사람의 담보(USDC)를 취합하여 `_mint`를 호출합니다. Gnosis CTF의 `splitPosition` 로직에 의해 $$1\ \ USDC \rightarrow \[1\ \ YES+ 1\ \ NO]$$로 변환됩니다.
* 결과: 시장의 전체 토큰 공급량이 증가하며, 기존 토큰 보유자가 없어도 새로운 유동성이 즉시 창출됩니다.

**MERGE (상보적 소멸)**

* 상태: 사용자 A("Yes" 매도)와 사용자 B("No" 매도)의 주문이 매칭됨.
* 기술적 동작: 컨트랙트는 두 사용자의 토큰을 회수하여 `_merge`를 호출합니다. `mergePositions` 로직에 의해 $$\[1\ \ Yes + 1\ \ No] \rightarrow 1\ \ USDC$$로 환원됩니다.
* 결과: 시장의 부채(발행된 토큰)가 줄어들고, 담보 자산이 사용자들에게 돌아갑니다.

**COMPLEMENTARY (일반 교환)**

* 상태: "Yes"를 사려는 사람과 이미 "Yes"를 보유한 판매자가 매칭됨.
* 기술적 동작: 추가적인 발행이나 소멸 없이, 단순한 자산 이동(`_transfer`)만으로 거래가 종료됩니다.

#### 3. 수학적 무결성 강제

이 메커니즘은 Polymarket 시장의 기초 물리 법칙을 온체인에서 강제합니다.

1. 자본 효율성: 누군가 'Yes'를 살 때 반드시 'Yes'를 팔 사람이 없어도 됩니다. 'No'를 살 사람만 있다면 즉시 자산을 발행하여 거래를 성사시킵니다.
2. 가격의 항등성: 모든 마켓에서 아래의 공식이 유지되도록 설계되어 있습니다.

   $$1\ \ Yes + 1\ \ No = 1\ \ USDC$$
3. 가스 최적화: 오퍼레이터가 여러 주문을 묶어 한 번의 `_mint` 또는 `_merge`로 처리함으로써 폴리곤 네트워크의 트랜잭션 비용을 효율적으로 관리합니다.

### Hybrid CLOB 분석

Polymarket은 거래 효율성을 높이기 위해 주문 매칭은 오프체인에서, 자산 이동(Settlement)은 온체인에서 수행하는 하이브리드 방식을 채택한다. `CTFExchange.sol`은 이 과정에서 최종적인 자산 교환을 보장하는 브릿지 역할을 수행한다.

#### 1) 오프체인 주문 생성 및 서명 (EIP-712)

{% code expandable="true" %}

```solidity
struct Order {
    /// @notice Unique salt to ensure entropy
    uint256 salt;
    /// @notice Maker of the order, i.e the source of funds for the order
    address maker;
    /// @notice Signer of the order
    address signer;
    /// @notice Address of the order taker. The zero address is used to indicate a public order
    address taker;
    /// @notice Token Id of the CTF ERC1155 asset to be bought or sold
    /// If BUY, this is the tokenId of the asset to be bought, i.e the makerAssetId
    /// If SELL, this is the tokenId of the asset to be sold, i.e the takerAssetId
    uint256 tokenId;
    /// @notice Maker amount, i.e the maximum amount of tokens to be sold
    uint256 makerAmount;
    /// @notice Taker amount, i.e the minimum amount of tokens to be received
    uint256 takerAmount;
    /// @notice Timestamp after which the order is expired
    uint256 expiration;
    /// @notice Nonce used for onchain cancellations
    uint256 nonce;
    /// @notice Fee rate, in basis points, charged to the order maker, charged on proceeds
    uint256 feeRateBps;
    /// @notice The side of the order: BUY or SELL
    Side side;
    /// @notice Signature type used by the Order: EOA, POLY_PROXY or POLY_GNOSIS_SAFE
    SignatureType signatureType;
    /// @notice The order signature
    bytes signature;
}


```

{% endcode %}

사용자는 직접 트랜잭션을 발생시키지 않고, `Order` 구조체에 맞춰 주문 내용을 작성한 뒤 자신의 개인키로 서명한다.

* 데이터 구조 (`OrderStructs.sol` 기반): 주문에는 가격, 수량, 주문 방향, `nonce`, 그리고 대상 `tokenId` 등이 포함된다.
* 서명 검증: `Hashing` 및 `Signatures` 믹스인을 통해 사용자의 서명을 검증합니다. 컨트랙트는 `keccak256`으로 주문을 해싱한 뒤 `ecrecover`를 통해 `maker`의 주소와 서명이 일치하는지 확인합니다.

#### 2) 온체인 정산 브릿지 (`matchOrders`)

오프체인에서 매수와 매도 주문이 일치하면, 오퍼레이터(Operator)가 해당 주문들을 모아 온체인에서 `matchOrders`를 호출하여 그안에 `_matchOrders()` 함수 로직이 진행됩니다.

{% code expandable="true" %}

```solidity
function matchOrders(
    Order memory takerOrder,
    Order[] memory makerOrders,
    uint256 takerFillAmount,
    uint256[] memory makerFillAmounts
) external nonReentrant onlyOperator notPaused {
    _matchOrders(takerOrder, makerOrders, takerFillAmount, makerFillAmounts);
}

/// @notice Matches orders against each other
/// Matches a taker order against a list of maker orders
/// @param takerOrder       - The active order to be matched
/// @param makerOrders      - The array of passive orders to be matched against the active order
/// @param takerFillAmount  - The amount to fill on the taker order, in terms of the maker amount
/// @param makerFillAmounts - The array of amounts to fill on the maker orders, in terms of the maker amount
function _matchOrders(
    Order memory takerOrder,
    Order[] memory makerOrders,
    uint256 takerFillAmount,
    uint256[] memory makerFillAmounts
) internal {
    uint256 making = takerFillAmount;

    (uint256 taking, bytes32 orderHash) = _performOrderChecks(takerOrder, making);
    (uint256 makerAssetId, uint256 takerAssetId) = _deriveAssetIds(takerOrder);

    // Transfer takerOrder making amount from taker order to the Exchange
    _transfer(takerOrder.maker, address(this), makerAssetId, making);

    // Fill the maker orders
    _fillMakerOrders(takerOrder, makerOrders, makerFillAmounts);

    taking = _updateTakingWithSurplus(taking, takerAssetId);
    uint256 fee = CalculatorHelper.calculateFee(
        takerOrder.feeRateBps, takerOrder.side == Side.BUY ? taking : making, making, taking, takerOrder.side
    );

    // Execute transfers

    // Transfer order proceeds post fees from the Exchange to the taker order maker
    _transfer(address(this), takerOrder.maker, takerAssetId, taking - fee);

    // Charge the fee to taker order maker, explicitly transferring the fee from the Exchange to the Operator
    _chargeFee(address(this), msg.sender, takerAssetId, fee);

    // Refund any leftover tokens pulled from the taker to the taker order
    uint256 refund = _getBalance(makerAssetId);
    if (refund > 0) _transfer(address(this), takerOrder.maker, makerAssetId, refund);

    emit OrderFilled(
        orderHash, takerOrder.maker, address(this), makerAssetId, takerAssetId, making, taking, fee
    );

    emit OrdersMatched(orderHash, takerOrder.maker, makerAssetId, takerAssetId, making, taking);

    
}
```

{% endcode %}

* `onlyOperator`: 일반 사용자가 직접 호출하는 것이 아니라, 중앙 매칭 엔진인 오퍼레이터만 실행 권한을 가집니다. 이는 오프체인 오더북과 온체인 상태의 완벽한 일치를 강제하며 프런트러닝을 차단합니다.
* 동작: 오퍼레이터는 테이커(Taker)의 주문 하나를 여러 명의 메이커(Maker) 주문과 매칭시켜 원자적으로(Atomic) 정산합니다.

#### 3) 자산 교환 실행 로직 (`_fillMakerOrder`)

실질적인 자산 이동은 `_matchOrders` 로직에 `_fillMakerOrder` 내부에서 `_fillFacingExchange`를 통해 수행됩니다.

{% code expandable="true" %}

```solidity
function _fillMakerOrder(Order memory takerOrder, Order memory makerOrder, uint256 fillAmount) internal {
    MatchType matchType = _deriveMatchType(takerOrder, makerOrder);
    
    // 두 주문 간의 유효성 검증 (ID, 수량, 가격 등)
    _validateTakerAndMaker(takerOrder, makerOrder, matchType);

    // 자산 ID 도출 (Yes, No 또는 담보 토큰 여부 확인)
    (uint256 makerAssetId, uint256 takerAssetId) = _deriveAssetIds(makerOrder);

    // 실제 자산 스왑 및 수수료 정산
    _fillFacingExchange(making, taking, makerOrder.maker, makerAssetId, takerAssetId, matchType, fee);

    emit OrderFilled(orderHash, makerOrder.maker, takerOrder.maker, ...);
}
```

{% endcode %}

* 상보적 매칭(Complementary Match): Polymarket의 특수 로직으로, Yes 주문과 No 주문이 만나면 담보 자산(USDC)으로 환원하거나, 담보 자산을 받아 Yes/No 토큰을 새로 발행(Mint)하는 로직이 `matchType`에 의해 결정됩니다.

## **Oracle & Data**

Polymarket에서는 외부 데이터를 가져오기 위해 아래  2곳에서 데이터를 얻어온다.

1. Chainlink
2. UMA

&#x20;Chainlink에서는 Crypto Market의 Price를 얻기 위해 가져오며, UMA는 외부 사실을 가져오기 위해 데이터를 들고온다.

* UMA Optimistic Oracle과의 인터페이스 분석.
* Data Sourcing: 가격 마켓의 경우 어떤 CEX나 DEX의 인덱스 가격을 참조하는지, 소스 데이터의 무결성을 어떻게 보장하는지 설명
* Resolution Request:
  * Propose & Bond: 결과를 제안하는 주체가 예치해야 하는 Bond의 규모와, 잘못된 제안을 했을 때 발생하는 Slashing 메커니즘을 분석합니다.
  * Reward 구조: 올바른 결과를 제안한 사람에게 주어지는 인센티브 로직을 설명합니다.

## **Governance**

분쟁 발생 시 합의 프로세스

* Dispute Mechanism: 누가, 어떤 조건(예: 잘못된 가격 데이터, 규칙 위반 제안)에서 이의를 제기할 수 있는지와 이에 필요한 비용을 분석
* UMA DVM Voting: 이의 제기 후 UMA의 `Data Verification Mechanism`으로 데이터가 넘어가는 과정과, UMA 토큰 홀더들의 투표 결과가 다시 Polymarket으로 돌아오는 Callback 구조를 분석합니다.
* Settlement Finality: 투표 결과에 따라 `Conditional Tokens`의 `payoutNumerators`가 업데이트되어 승리 지분의 가치가 1$로 확정되는 과정.

​

## **Operation**

시스템의 관리 권한과 보안 리스크 제어 지점 분석

* Access Control: `Owner` 및 `Admin` 권한을 가진 주소들의 역할(시장 생성 권한, 수수료 설정 권한 등).
* Upgradeability: 프록시 패턴 사용 여부와 로직 업데이트 시의 타임락(Timelock) 적용 여부.
* Emergency Infrastructure (비상 제어 체계)
  * Circuit Breakers: 비정상적인 가격 변동이나 외부 공격 감지 시 `pause()` 함수가 활성화되는 조건과 영향 범위를 분석합니다.
  * Withdrawal Guardians: 프로토콜 중단 시 사용자가 자산을 안전하게 출금할 수 있는 비상 탈출구(Escape Hatch) 존재 여부를 점검합니다.
* Revenue & Fee Distribution (수수료 정산 및 분배)
  * Fee Accumulation: 거래 발생 시점에 즉시 징수되는지, 아니면 시장 종료 후 정산 시점에 일괄 징수되는지 분석합니다.
  * Distribution Flow: 수수료가 운영사 주소로 직접 전송되는지, 아니면 별도의 `FeePool` 컨트랙트를 거쳐 거버넌스 토큰 홀더 등에게 배분되는지 확인합니다.
* Operational Lifecycle Management (마켓 생명주기 운영)
  * Market Finalization: 오라클 결과 도출 후 시장이 'Resolved' 상태로 전환되는 오프체인/온체인 트리거 방식을 분석합니다.
  * Inactivity Management: 운영사가 매칭을 중단하거나 오라클 응답이 지연될 경우, 시장을 강제 종료하고 환불을 처리하는 운영 정책을 검토합니다.

[^1]:


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://funarchy.gitbook.io/funarchy/introduction/analyzed-target/polymarket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
