> ## Documentation Index
> Fetch the complete documentation index at: https://docs.optimism.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 스크립팅 엔진

> OP Deployer의 인메모리 EVM 스크립팅 엔진을 이해합니다. 엔진이 가능하게 하는 것, 온체인 상호작용이 Solidity 스크립트를 통해 실행되는 이유, 그리고 Go 코드가 시뮬레이션된 EVM 내부의 스크립트와 통신하는 방식을 설명합니다.

OP Deployer의 가장 강력한 기능 중 하나는 인메모리 EVM 스크립팅 엔진입니다. 스크립팅 엔진은 Forge와
유사한 기능을 제공합니다.

* 모든 온체인 호출을 먼저 시뮬레이션 환경에서 실행하므로, gas 비용이 발생하기 전에 온체인 호출의
  효과를 검증할 수 있습니다.
* Foundry cheatcode를 제공하여 EVM 환경을 깊이 있게 계측하고 사용자 지정할 수 있습니다. 이러한
  cheatcode 덕분에 OP Deployer가 Solidity 스크립트를 호출할 수 있습니다.

스크립팅 엔진은 사실상 OP Deployer의 핵심입니다. 스크립팅 엔진이 없다면 OP Deployer는 Forge를 감싼
얇은 래퍼에 불과할 것입니다. 스크립팅 엔진은 다음을 가능하게 합니다.

* 기존 Solidity 기반 도구와의 손쉬운 통합.
* 배포 실패 시 상세한 스택 트레이스.
* 실패할 수 있는 온체인 트랜잭션 전송을 방지하는 빠른 피드백 루프.
* 라이브 체인 포킹.

이러한 이유 등으로 스크립팅 엔진은 OP Deployer 아키텍처의 핵심 요소입니다. OP Deployer가 시작하는
거의 모든 온체인 상호작용은 스크립팅 엔진을 사용하여 Solidity 스크립트를 호출합니다. 그러면 스크립트는
`vm.broadcast`를 사용하여 온체인으로 전송해야 할 트랜잭션을 알립니다.

## Solidity 스크립트를 사용하는 이유

복잡한 온체인 상호작용에서는 Solidity 스크립트가 Go 코드보다 훨씬 사용하기 편리합니다. Solidity 스크립트는
다음을 가능하게 합니다.

* 기존 Solidity 기반 도구 및 라이브러리와의 손쉬운 통합.
* 간단한 ABI 인코딩/디코딩.
* 컨트랙트 간 호출과 이를 구동하는 기반 RPC 호출 간의 명확한 관심사 분리.

대안은 모든 온체인 상호작용을 Go 코드로 작성하는 것입니다. 이는 가능하지만 훨씬 장황하며, Go와
Solidity ABI 간의 바인딩을 작성해야 합니다. 이러한 바인딩은 오류가 발생하기 쉽고 유지 관리가 어렵습니다.

## 엔진 구현

스크립팅 엔진은 `op-chain-ops/script` 패키지에 구현되어 있습니다. Geth의 EVM 구현을 Forge cheatcode로
확장하고, Go 구조체를 EVM 메모리에 etch할 수 있는 몇 가지 도구를 정의합니다. 엔진 동작의 대부분은 Geth가
제공하는 hook에 의해 구동됩니다. 이를 더 깊이 이해하는 가장 좋은 방법은 코드를 읽는 것입니다.

## 엔진 사용

OP Deployer는 위에서 설명한 etching 도구를 사용하여 OP Deployer와 스크립팅 엔진 간에 통신합니다.
대부분의 Solidity 스크립트는 입력 컨트랙트, 출력 컨트랙트, 그리고 스크립트 자체를 정의합니다. 스크립트는
실행되면서 입력 컨트랙트의 필드에서 데이터를 읽고 출력 컨트랙트의 필드를 설정합니다. OP Deployer는 입력 및
출력 컨트랙트를 다음과 같이 Go 구조체로 정의합니다.

```go theme={null}
package foo_script

type FooInput struct {
  Number uint64
  Bytes []byte
}

type FooOutput struct {
  Result uint64
  Bytes []byte
}
```

그런 다음 입력 및 출력 컨트랙트는 다음과 같이 EVM 메모리에 "etch"됩니다.

```go theme={null}
package foo_script

// ... struct defs elided

func Run(host *script.Host, input FooInput) (FooOutput, error) {
	// Create a variable to hold our output
	var output FooOutput

	// Make new addresses for our input/output contracts
	inputAddr := host.NewScriptAddress()
	outputAddr := host.NewScriptAddress()

	// Inject the input/output contracts into the EVM as precompiles
	cleanupInput, err := script.WithPrecompileAtAddress[*FooInput](host, inputAddr, &input)
	if err != nil {
		return output, fmt.Errorf("failed to insert input precompile: %w", err)
	}
	defer cleanupInput()

	cleanupOutput, err := script.WithPrecompileAtAddress[*FooOutput](host, outputAddr, &output,
		script.WithFieldSetter[*FooOutput])
	if err != nil {
		return output, fmt.Errorf("failed to insert output precompile: %w", err)
	}
	defer cleanupOutput()

	// ... do stuff with the input/output contracts ...
}
```

스크립트 엔진은 입력 및 출력 컨트랙트의 필드에 대한 getter와 setter를 자동으로 생성합니다.
`evm:` 구조체 태그를 사용하여 이러한 getter와 setter의 동작을 사용자 지정할 수 있습니다.

마지막으로 스크립트 자체가 다음과 같이 EVM 메모리에 etch되어 실행됩니다.

```go theme={null}
package foo_script

type FooScript struct {
	Run func(input, output common.Address) error
}

func Run(host *script.Host, input FooInput) (FooOutput, error) {
	// .. see implementation above...

	deployScript, cleanupDeploy, err := script.WithScript[FooScript](host, "FooScript.s.sol", "FooScript")
	if err != nil {
		return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err)
	}
	defer cleanupDeploy()

	if err := deployScript.Run(inputAddr, outputAddr); err != nil {
		return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err)
	}

	return output, nil
}
```

스크립트가 파일에서 로드된다는 점을 알 수 있습니다. 스크립팅 엔진을 실행하려면 스크립팅 엔진이 사용할
컨트랙트 artifact(소스 코드가 **아님**)가 디스크 어딘가에 있어야 합니다. 자세한 내용은
[Artifacts Locator](https://docs.optimism.io/ko/chain-operators/tools/op-deployer/reference/artifacts-locators.md) 페이지를 참고하십시오.
