저수준 호출(low-level call)을 수행하는 세 가지 함수의 예시를 살펴보자.
call- 외부 컨트랙트 또는 계정에 일반 호출을 수행staticcall- 읽기 전용(static) 호출 수행 (상태 변경 불가능)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→ 이 호출에 사용할 가스 한도
🔹 내부 동작
어셈블리(assembly) 블록 사용:
assembly ("memory-safe") {assembly키워드는 EVM 어셈블리 코드를 직접 작성할 수 있도록 해준다."memory-safe"옵션은 Solidity 0.8.12 이상에서 도입된 메모리 안전 기능을 보장한다.
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);
위 코드는 targetAddress에 someFunction(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함수 실행 등)에 사용됨. staticcall은 ETH 전송이 불가능하며, 상태 변경이 있는 함수를 호출하려 하면 실패한다.
🔹 매개변수 설명
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);
targetContract의upgradeImplementation(address)함수를 현재 컨트랙트 컨텍스트에서 실행함.
🚀 정리
| 함수 | 목적 | 상태 변경 | msg.sender 유지 |
ETH 전송 가능 |
|---|---|---|---|---|
call |
외부 컨트랙트 함수 실행 | ✅ 가능 | ❌ 변경됨 | ✅ 가능 |
staticcall |
읽기 전용 함수 실행 | ❌ 불가능 (view 함수만 가능) |
❌ 변경됨 | ❌ 불가능 |
delegateCall |
Proxy 패턴 / 컨트랙트 코드 재사용 | ✅ 가능 | ✅ 유지됨 | ✅ 가능 |
✔ Proxy 패턴을 사용할 때 delegateCall이 필수적임
✔ EVM 저수준 호출을 직접 다루기 위해 call과 staticcall을 활용 가능
✔ 메모리 접근 방식(add(data, 0x20), mload(data))을 통해 호출 데이터를 효과적으로 다룸
'Solidity' 카테고리의 다른 글
| 재진입공격 방지기법-nonReentrant (0) | 2025.02.21 |
|---|---|
| Solidity 에서 메모리 읽기 - mload() 와 add() (0) | 2025.02.20 |
| assembly ("memory-safe"){} 문법 (1) | 2025.02.01 |
| Solidity 개발 시, 가스비 절감을 위한 최적화 기법 (1) | 2025.01.03 |
| Ethereum 에서 사용할 수 있는 주요 오라클 서비스 (2) | 2025.01.03 |