저수준 호출(low-level call)을 수행하는 세 가지 함수의 예시를 살펴보자.

  1. call - 외부 컨트랙트 또는 계정에 일반 호출을 수행
  2. staticcall - 읽기 전용(static) 호출 수행 (상태 변경 불가능)
  3. delegateCall - 컨트랙트의 컨텍스트에서 다른 컨트랙트의 코드를 실행

이 모든 함수는 Solidity의 어셈블리 코드(assembly) 블록을 사용하여 직접 EVM의 opcodes를 호출하는 방식으로 구현되었다.


📌 1. call 함수 분석

function call(
    address to,
    uint256 value,
    bytes memory data,
    uint256 txGas
) internal returns (bool success) {
    assembly ("memory-safe") {
        success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
    }
}

🔹 함수의 의미

이 함수는 주어진 주소(to)로 일반적인 이더리움 트랜잭션을 실행하는 역할을 한다.
즉, 특정 컨트랙트나 EOA(Externally Owned Account)에 call을 통해 실행 요청을 보낼 수 있다.


🔹 매개변수 설명

  • address to → 호출할 대상 주소 (EOA 또는 컨트랙트)
  • uint256 value → 전송할 ETH 양 (wei 단위)
  • bytes memory data → 실행할 함수의 바이너리 데이터(ABI 인코딩된 callData)
  • uint256 txGas → 이 호출에 사용할 가스 한도

🔹 내부 동작

  1. 어셈블리(assembly) 블록 사용:

     assembly ("memory-safe") {
    • assembly 키워드는 EVM 어셈블리 코드를 직접 작성할 수 있도록 해준다.
    • "memory-safe" 옵션은 Solidity 0.8.12 이상에서 도입된 메모리 안전 기능을 보장한다.
  2. EVM의 call 오퍼코드 실행

     success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
    • call(txGas, to, value, input_data, input_size, output_data, output_size)
    • 각 인수의 의미:
      • txGas: 사용할 가스 양
      • to: 호출 대상 주소
      • value: 전송할 이더 값
      • add(data, 0x20): data의 실제 데이터 시작 위치 (Solidity에서는 첫 32바이트가 길이 정보)
      • mload(data): data의 길이 (바이트 단위)
      • 0, 0: 출력 위치 및 크기 (결과를 무시하는 경우)

🔹 실행 결과

  • success 값이 true이면 호출이 성공했음을 의미.
  • false이면 호출이 실패했음을 의미.

📌 예제 사용법

bool success = Exec.call(targetAddress, 1 ether, abi.encodeWithSignature("someFunction(uint256)", 42), 50000);

위 코드는 targetAddresssomeFunction(uint256) 함수를 호출하면서 1 ETH를 전송하고, 가스로 50000을 할당한다.


📌 2. staticcall 함수 분석

function staticcall(
    address to,
    bytes memory data,
    uint256 txGas
) internal view returns (bool success) {
    assembly ("memory-safe") {
        success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0)
    }
}

🔹 함수의 의미

이 함수는 읽기 전용(view)으로 컨트랙트 함수를 실행할 때 사용한다.

  • 상태 변경이 없는 호출(예: 읽기 전용 함수 조회, view 또는 pure 함수 실행 등)에 사용됨.
  • staticcallETH 전송이 불가능하며, 상태 변경이 있는 함수를 호출하려 하면 실패한다.

🔹 매개변수 설명

  • address to → 호출할 컨트랙트 주소
  • bytes memory data → ABI 인코딩된 함수 호출 데이터
  • uint256 txGas → 호출에 사용할 가스 한도

🔹 내부 동작

success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0)
  • staticcall(txGas, to, input_data, input_size, output_data, output_size)
  • 특징:
    • call과 거의 동일하지만, 상태 변경이 불가능함.
    • ETH를 보낼 수 없음.
    • view 또는 pure 함수만 호출 가능.

🔹 실행 결과

  • 호출이 성공하면 true, 실패하면 false를 반환.

📌 예제 사용법

bool success = Exec.staticcall(targetAddress, abi.encodeWithSignature("getBalance()"), 50000);
  • targetAddress 컨트랙트에서 getBalance() 함수를 호출함.

📌 3. delegateCall 함수 분석

function delegateCall(
    address to,
    bytes memory data,
    uint256 txGas
) internal returns (bool success) {
    assembly ("memory-safe") {
        success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
    }
}

🔹 함수의 의미

이 함수는 호출하는 컨트랙트의 컨텍스트를 유지한 채(msg.sender 유지) 다른 컨트랙트의 코드를 실행한다.

  • Proxy 패턴에서 핵심적으로 사용됨.
  • 상태 변수는 호출하는 컨트랙트(호출자)의 것을 사용.

🔹 매개변수 설명

  • address to → 호출할 컨트랙트 주소
  • bytes memory data → ABI 인코딩된 함수 호출 데이터
  • uint256 txGas → 호출에 사용할 가스 한도

🔹 내부 동작

success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
  • delegatecall(txGas, to, input_data, input_size, output_data, output_size)
  • 특징:
    • msg.sender는 변경되지 않음 (호출한 원래 주소 그대로 유지됨).
    • 상태 변경은 호출된 컨트랙트(to)가 아니라, 호출하는 컨트랙트(caller 또는 Proxy)의 상태 변수에 영향을 줌.
    • Proxy 패턴 구현에 사용됨.

🔹 실행 결과

  • 호출이 성공하면 true, 실패하면 false를 반환.

📌 예제 사용법

bool success = Exec.delegateCall(targetContract, abi.encodeWithSignature("upgradeImplementation(address)", newImplementation), 50000);
  • targetContractupgradeImplementation(address) 함수를 현재 컨트랙트 컨텍스트에서 실행함.

🚀 정리

함수 목적 상태 변경 msg.sender 유지 ETH 전송 가능
call 외부 컨트랙트 함수 실행 ✅ 가능 ❌ 변경됨 ✅ 가능
staticcall 읽기 전용 함수 실행 ❌ 불가능 (view 함수만 가능) ❌ 변경됨 ❌ 불가능
delegateCall Proxy 패턴 / 컨트랙트 코드 재사용 ✅ 가능 ✅ 유지됨 ✅ 가능

Proxy 패턴을 사용할 때 delegateCall이 필수적임
EVM 저수준 호출을 직접 다루기 위해 callstaticcall을 활용 가능
메모리 접근 방식(add(data, 0x20), mload(data))을 통해 호출 데이터를 효과적으로 다룸

+ Recent posts