> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/basics/2./undefined-2/call-vs-delegatecall.md).

# call vs delegatecall

스마트 컨트랙트는 외부 컨트랙트와의 상호작용을 통해 복잡한 로직을 구현할 수 있으며, 이러한 상호작용의 핵심에는 call과 delegatecall이라는 두 가지 low-level 함수가 있다. 이 함수들은 모두 특정 컨트랙트가 다른 컨트랙트 함수를 실행할 수 있도록 도와주지만, 실제로 어떤 컨트랙트의 스토리지가 변경되는지 차이점이 존재한다.

## call

call은 스마트 컨트랙트가 외부 컨트랙트의 함수나 주소로 직접 메세지를 전송할 수 있게 해주는 명령어이다. 주로 함수 호출, eth 전송, 인터페이스 호출 등에 사용된다.

<figure><img src="https://2462457462-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLf9vwRuaws92ULgGvzTi%2Fuploads%2Fb9J4NUqLQWek8HoAADcu%2Fimage.png?alt=media&amp;token=9dffb0f2-2043-4b88-8e5b-e171114f45b1" alt=""><figcaption></figcaption></figure>

컨트랙트 A가 call 명령어로 컨트랙트 B의 함수를 호출할 수 있다. B는 A가 요청한 함수를 B 내부에서 실행하고 로직에 따라 자신(B)의 스토리지를 변경하게 된다.

{% hint style="info" %}
두번째 과정에서 contract B의 함수를 contract A가 호출한다는 점에서 msg.sender는 컨트랙트 A의 주소가 된다.
{% endhint %}

### **예시**

```solidity
contract Target {
     uint public value;

    function setValue(uint _v) public {
        value = _v;
    }
}

contract Caller {
    function callSetValue(address _target) public {
        (bool success, ) = _target.call(
            abi.encodeWithSignature("setValue(uint256)", 42)
        );
        require(success, "Call failed");
    }
}

```

위의 그림 예시에 적용시켰을 때, A는 Caller 컨트랙트, B는 Target 컨트랙트에 해당한다.

외부 사용자가 Target 컨트랙트의 주소와 함께 callSetValue 함수를 호출하면 call 명령어가 실행된다. 이후, setValue(uint256) 형태의 함수와 인자값 42를 인코딩하여 Target 컨트랙트를 호출한다. Target 컨트랙트에서 setValue(42)를 호출하고 value 값을 42로 변경시킨다.

최종적으로 Target 컨트랙트의 스토리지가 변경되고 success 값은 true로 require을 만족시킨다.

***

## delegatecall

delegatecall은 스마트 컨트랙트가 외부 컨트랙트의 코드를 실행하며 변경된 값을 자신의 스토리지에 반영하는 명령어이다. 이는 call과 다르게 delegatecall을 실행한 컨트랙트의 스토리지가 바뀐다는 차이점이 있다. delegatecall은 일반적인 호출 방식과 다르다는 특징을 가지기 때문에 주로 컨트랙트 업데이트에 사용이 된다.

그리고 delegatecall에서 등장하는 컨트랙트 개념인 proxy contract와 logic contract가 있다.

* **proxy contract**: 상태(스토리지)를 보관하고 실제 로직을 실행할 logic contract로 위임하는 컨트랙트
* **logic contract**: 함수(코드)를 정의하며, proxy에 의해 호출되어 proxy의 스토리지를 조작하는 컨트랙트

<figure><img src="https://2462457462-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLf9vwRuaws92ULgGvzTi%2Fuploads%2FrKMnKczxisSYI7UpLyn5%2Fimage.png?alt=media&amp;token=91a54034-f86e-49ac-82a8-089cbe7fd848" alt=""><figcaption></figcaption></figure>

그림에서 proxy contract는 컨트랙트 A, logic contract는 컨트랙트 B에 해당한다.

컨트랙트 A가 delegatecall 명령어를 사용해서 컨트랙트 B를 호출하게 되면, 컨트랙트 B의 함수를 사용하지만 컨트랙트 A의 스토리지 값들을 이용해서 컨트랙트 A를 변경하게 된다.

delegatecall은 함수 실행을 외부컨트랙트에 맡기며 스토리지 변경은 자신이 맡기 때문에 "위임"이라는 형태로도 불린다. 상태를 그대로 외부 컨트랙트로 전달하기 때문에 msg.sender와 address(this) 값은 각각 EOA와 컨트랙트 A의 주소로 유지된다.

한번 배포된 컨트랙트는 수정이 불가능하다는 단점이 존재한다. 따라서 업그레이드 사항을 반영하기 위해 delegatecall을 사용해서 처리한다. 기능이 업그레이드 된 logic contract를 배포하고 proxy contract가 참조하는 주소를 변경하고, 필요한 함수들을 호출시켜 proxy contract에 새로운 로직을 반영할 수 있다.

### **예시**

```solidity
// Logic contract (code 제공자)
contract Logic {
    uint public value;

    function setValue(uint _v) public {
        value = _v;
    }
}
```

```solidity
// Proxy contract (storage 소유자)
contract Proxy {
    uint public value;
    uint public owner;
    address public logicContract; // Logic contract 주소를 저장

    constructor(address _logic) {
        owner = msg.sender;
        logicContract = _logic;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not Authorized");
        _;
    }
    
    function upgradeTo(address _newLogic) public onlyOwner {
        logicContract = _newLogic;
    }

    function delegateSetValue(uint _v) public {
        (bool success, ) = logicContract.delegatecall(
            abi.encodeWithSignature("setValue(uint256)", _v)
        );
        require(success, "Delegatecall failed");
    }
}

```

외부 사용자가 delegateSetValue 함수를 호출하면 기존에 설정되었던 logicContract 주소를 대상으로 delegatecall을 호출한다. 함수 실행을 Logic 컨트랙트에 전적으로 위임하기 때문에, setValue를 인자값 v와 함께 실행하게 되어 Proxy 컨트랙트 value 값에 \_v가 저장된다.

proxy 컨트랙트의 storage 값으로 함수를 실행하지만, 실행값이 변경되는 위치는 Logic 컨트랙트를 기준으로 proxy 컨트랙트에 저장된다.  위의 예시에서 value 변수는 각각 컨트랙트마다 변수 위치가 동일하지만, 잘못 작성되었을 경우 proxy 컨트랙트에 예상치 못한 값이 반영될 수 있기 때문에 **delegatecall은 주의해서 사용해야 한다.**

### UUPS(Universal Upgradeable Proxy Standard)

이전 컨트랙트 형식과 달리 업그레이드 기능을 **Logic 컨트랙트에 포함**시키는 작성 표준을 말한다. 이전 작성 방법인 Transparent Proxy 패턴은 proxy 컨트랙트가 직접 upgrade 권한을 가지고 실행했지만, UUPS에서는 logic 컨트랙트가 **자기 스스로를 업그레이드** 할 수 있게 만든 구조이다.

UUPS 구조에서 proxy 컨트랙트는 logic 컨트랙트를 delegatecall만 진행하기 때문에 컨트랙트를 경량화 할 수 있다는 장점이 있다.

#### **예시**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UUPSLogic {
    uint256 public value;
    address public implementation;

    function setValue(uint256 _v) public {
        value = _v;
    }

    function upgradeTo(address newImpl) public {
        // onlyOwner 생략
        bytes32 implSlot = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
        assembly {
            sstore(implSlot, newImpl)
        }
    }
}
```

**실행 과정**

1. proxy 컨트랙트의 delegatecall 실행 -> UUPSLogic.upgradeTo 실행
2. UUPSLogic 컨트랙트는 proxy 컨트랙트의 EIP-1967 슬롯 위치를 직접 찾아감
3. 해당 슬롯에 새로운 implementation 저장
4. 이로 인해 proxy 컨트랙트의 implementation 주소가 즉시 교체됨

이 과정에서 proxy 컨트랙트는 fallback으로 delegatecall을 실행하게 됨

[출처](https://docs.openzeppelin.com/contracts/4.x/api/proxy)
