✏️ Solidity 문법
❗️변수
1️⃣ Local 변수
- 함수 내부에서 선언합니다.
- 블록체인에 저장되지 않습니다.
2️⃣ State 변수
- 함수 외부에서 선언합니다.
- 블록체인에 저장됩니다.
3️⃣ Global 변수
- 블록체인에 대한 정보를 제공합니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Variables {
// 블록체인에 저장되는 상태 변수입니다.
string public text = "Hello";
uint public num = 123;
function doSomething() public {
// 블록체인에 저장되지 않는 로컬 변수입니다.
uint i = 456;
// 블록체인에 대한 정보를 제공해주는 글로벌 변수입니다.
uint timestamp = block.timestamp;
address sender = msg.sender;
}
}
❗️ 접근 제어자
1️⃣ Internal
Contract 내부 혹은 상속된 Contract에서 호출 가능합니다.
2️⃣ External
Contract 외부에서만 호출 가능합니다.
3️⃣ Public
해당 함수를 Contract 내부/외부 모두에서 호출 가능합니다.
4️⃣ Private
해당 함수를 Contract 내부에서만 호출 가능합니다.
❗️ 함수 타입 제어자
1️⃣ View
- 함수밖에 데이터를 읽을 순 있으나 데이터를 변경할 수 없습니다.
2️⃣ Pure
- 함수밖에 데이터를 읽지 못합니다.
- 가스비가 발생하지 않습니다.
- 인자값만 사용해서 반환값을 정합니다.
3️⃣ Payable
- 함수가 ETH를 받을 수 있게 합니다.
- 가스비가 발생합니다.
❗️ 값 타입
1️⃣ 논리 자료형 ( Boolean )
- True 또는 False입니다.
2️⃣ 정수형 ( Int, Uint )
- int8 ~ int256 : 8 ~ 256 Bit의 양수 음수 저장 가능합니다.
- uint8 ~ uint256 : 8 ~ 256 Bit의 양수만 저장 가능합니다.
3️⃣ 주소형 ( Address )
- address : 20Byte의 이더리움 지갑 주소입니다.
- address(0) : 주소 0(0x00000000... 00)을 의미합니다. 주로 토큰을 소각할 때 사용합니다.
- address(this) : 해당 Contract Address를 의미합니다.
4️⃣ 문자열 ( String )
- string : String 타입은 가스비용이 더 들어가기 때문에 32Byte 이상일 때만 string 타입을 사용합니다.
❗️ 열거형 ( Enum )
서로 연관된 상수들을 묶어 값을 지정하는 자료형입니다.
enum medal {gold,silver,bronze } // gold = 0, silver = 1, bronze = 2
enum Ranking {gold = 1, silver, bronze } // gold = 1, silver = 2, bronze = 3
❗️ 참조 타입
1️⃣ Storage
- 변수를 블록체인상에 영구히 저장합니다. / Ex : 하드디스크
2️⃣ Memory
- 임시 저장 변수, 함수가 종료되면 휘발됩니다. / Ex : Ram
❗️ Struct ( 구조체 )
관련된 변수들을 하나의 그룹으로 묶어서 사용할 수 있는 사용자 정의 데이터 타입입니다.
struct Person {
string name;
uint8 age;
}
❗️ Array ( 배열 )
1️⃣ 동적배열
- 배열의 길이가 무한대로 늘어납니다.
uint[] public arr;
2️⃣ 정적배열
- 미리 배열의 최대 길이를 지정합니다.
uint[10] public arr
❗️ Mapping ( 매핑 )
- Key & Value 형식으로 데이터를 저장합니다.
- Key Type은 동적배열, 열거형, 구조체, 매핑을 제외한 모든 타입 가능합니다.
- Value Type은 모든 타입 가능합니다.
mapping(address => uint256) balance;
❗️ Modifier ( 함수 변경자 )
Modifier를 사용하면 사전에 어떤 조건에 부합되는지 확인 가능합니다. 예를 들어 함수를 실행시키기 전, 스마트컨트랙트를 배포한 사람의 계정과 동일한 계정인지 확인 가능합니다.
modifier check {
count ++;
_;
count --;
}
function setCount() check public {
if(mutex == 1) exeCnt = exeCnt + 1;
}
❗️ Virtual , Override
- Virtual Keyword를 사용한 함수는 덮어쓸 수 있습니다.
function balanceOf(address owner) public view virtual returns (uint256){
require(owner != msg.sender);
return 100;
}
- Override Keyword를 사용한 함수는 덮어쓴 함수입니다.
function balanceOf(address owner) public view override returns (uint256){
require(owner != msg.sender);
uint256 a = 100
return a;
}
❗️ Event , Emit , Indexed
- Event를 사용하여 value를 확인할 수 있습니다.
- Emit을 사용하여 Event를 블록에 담을 수 있습니다.
- Indexed를 사용하면 블록에서 출력된 Event를 필터링하여 특정 Event만 확인할 수 있습니다.
contract eventTest {
event testEvent(address wallet, uint256 money);
event testEvent2(address indexed wallet, uint256 money);
function sendMoney() public {
emit testEvent(address(0),10000)
emit testEvent2(address address(0), uint256 money);
}
}
❗️ Event log 확인
서버에서 Transaction 내용을 가지고 Event log를 decode 하여 확인할 수 있습니다.
/** Transaction Sample */
/**
{
blockHash: '0xb615dcd7cdd5155299cbd381f51315fad97115b6675c95006efa81df6bf0cc75',
blockNumber: 16073981,
contractAddress: null,
cumulativeGasUsed: 12120252,
effectiveGasPrice: 11115226078,
from: '0xd6150f8b9e586fbc36f498ff9a1d50525e406fa8',
gasUsed: 35293,
logs: [
{
address: '0x57ad75C0D3C9b97Ad4Bc8225Fab4c9EFedbC81Ef',
blockHash: '0xb615dcd7cdd5155299cbd381f51315fad97115b6675c95006efa81df6bf0cc75',
blockNumber: 16073981,
data: '0x0000000000000000000000000000000000000000000000004563918244f40000',
logIndex: 257,
removed: false,
topics: [Array],
transactionHash: '0xec98e7506fa59be6ea3c546e3ce2979b4d847e0784384af2e68fd7895858e127',
transactionIndex: 128,
id: 'log_bb167d08'
}
],
logsBloom: '0x00000000000000000200000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000020000000000000008000000000000000000008000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010008000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000',
status: true,
to: '0x57ad75c0d3c9b97ad4bc8225fab4c9efedbc81ef',
transactionHash: '0xec98e7506fa59be6ea3c546e3ce2979b4d847e0784384af2e68fd7895858e127',
transactionIndex: 128,
type: '0x0'
}
*/
const eventLog = web3.eth.abi.decodeLog(
[
{
type: 'address',
name: 'from',
indexed: true,
},
{
type: 'address',
name: 'to',
indexed: true,
},
{
type: 'uint256',
name: 'value',
},
],
result.logs[0].data,
[result.logs[0].topics[1], result.logs[0].topics[2]],
);
console.log(eventLog)
/**
Result {
'0': '0xd6150F8b9E586FBc36F498FF9A1D50525E406fA8',
'1': '0xe89a6122C127055FbDb3Df2BaEB3A4FfDaBb4711',
'2': '5000000000000000000',
__length__: 3,
from: '0xd6150F8b9E586FBc36F498FF9A1D50525E406fA8',
to: '0xe89a6122C127055FbDb3Df2BaEB3A4FfDaBb4711',
value: '5000000000000000000'
}
*/
❗️ Abstract Function ( 추상 함수 )
- Abstract Function는 함수의 이름과 입력 매개 변수와 출력 매개 변수만 선언해 두고 내용은 없는 함수입니다.
- 내용이 없기 때문에 중괄호 끝에 ;만 붙이면 됩니다.
function name(x1,...,xN) Option returns(y1,...,yN)
❗️ Abstract Contract ( 추상 컨트랙트 )
- Abstract Contract는 컨트랙트를 만드는 기본틀로 활용됩니다.
- 단순히 컨트랙트 내부에서 하나 이상의 함수가 추상 함수이면 자동으로 추상 컨트랙트가 됩니다.
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
❗️ Using A For B
라이브러리 A의 모든 유형 B로 첨부합니다.
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract LockupContract {
using SafeERC20 for IERC20;
}
❗️ Super
유효성 검사를 포함하지 않기 때문에 일반 산술 연산에 비해 더 작은 바이트코드를 생성하여 가스비를 줄이는 역할을 합니다.
- Use Unchecked
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20 : transfer amount exceeds balance");
unchecked{
// no validation here as it's a already validated in the `require()` condition
_balances[sender] = senderBalance - amount;
}
- Unused Unchecked
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20 : transfer amount exceeds balance");
// redundant validation here
_balances[sender] = senderBalance - amount;
❗️ Assembly
어셈블리는 EVM 바이트코드를 더욱 직접적으로 제어하게 해주는 저수준 프로그래밍 요소로, 최적화 및 더 복잡한 연산을 수행하기 위해 사용됩니다.
- inline Assembly안에서 변수를 할당할 때는 =가 아닌 :=로 할당해야 합니다.
assembly {
let A := 5
}
- assembly는 블록 간 namespace를 공유하지 않습니다.
assembly {
let x := 5
}
assembly {
let y := x /** Error */
}
- assembly를 사용하여 가스 비용을 절감할 수 있습니다.
function addAssembly(uint x, uint y) public pure returns (uint) {
assembly {
// 변수 result를 선언하여 :=를 통해 x+y 값을 변수에 할당
let result := add(x, y)
// mstore opcode를 사용하여 메모리 주소 0x0에 result 값을 저장합니다.
mstore(0x0, result)
// 0x0 메모리 주소에 32Byte를 반환합니다.
return(0x0, 32)
}
}
function addSolidity(uint x, uint y) public pure returns (uint) {
return x + y;
}
- assembly 내부에서 문자열을 선언할 때 32Byte보다 클 경우 에러가 발생합니다.
assembly {
let Text := '32Byte보다 큰 문자열' /** Error */
}
- assembly 내부에서 변수는 블록 범위 지정의 표준 규칙을 따릅니다. 블록 범위는 {...}로 구분합니다.
assembly {
let x := 5
{
let y := x
}
{
let z := y /** Error */
}
}
- assembly에서 for문은 아래와 같이 작성할 수 있습니다.
assembly {
/**
* { let i := 0 } = 변수 i 선언 및 0 할당
* lt(i, n) = 조건으로 i < n을 의미하며 양수만 비교 가능합니다.
* { i := add(i, 1) } = 반복 동작으로 i++을 의미합니다.
*/
for { let i := 0 } lt(i, n) { i := add(i, 1) } {
value := mul(2 , value)
}
mstore(0x0, value)
return(0x0, 32)
}
- assembly에서 if문은 아래와 같이 작성할 수 있습니다. else는 지원하지 않습니다.
assembly {
/** slt(x, 0) = x < 0을 의미하며 양수 음수 모두 비교 가능합니다. */
/** sub(0, x) = 0 - x를 의미합니다. */
if slt(x, 0) {
x := sub(0, x)
}
}
- assembly에서 switch문은 아래와 같이 작성할 수 있습니다. case문에서 모든 유형이 포함된 경우 default case는 허용되지 않습니다.
assembly {
let a := 5
switch lt(a,10)
case true {
/** DO SOMETHING */
}
case false {
/** DO SOMETHING */
}
defalut {
/** This is not allowed */
}
}
❗️ abi.encode, abi.encodePacked
1️⃣ ABI encode
solidity에서 제공하는 EVM에 대한 표준 인코딩 규칙으로 인코딩하여 EVM이 읽을 수 있도록 합니다.
2️⃣ ABI encodePacked
데이터를 인코딩하는데 필요한 최소한의 메모리만( 압축 인코딩 ) 사용합니다. 동적유형 (배열, 문자열 등)은 길이 없이 있는 그대로 인코딩 됩니다. 32 Bytes보다 짧은 유형은 0으로 채워지지 않습니다.
abi.encode("Test")
// bytes:0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045465737400000000000000000000000000000000000000000000000000000000
abi.encodePacked("Test")
// bytes: 0x54657374
❗️ abi.encodeWithSelector, abi.encodeWithSignature, abi.encodeCall
1️⃣ ABI encodeWithSelector
call 메서드로 다른 컨트랙트의 기능을 호출할 때 사용하며, 2번째 매개변수 부터 인코드하여 함수 선택자(1번째 매개변수) 앞에 붙입니다.
interface IERC20 {
function transfer(address, uint) external;
}
function encodeWithSelector(address to, uint amount) external pure returns (bytes memory) {
return abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
}
2️⃣ ABI encodeWithSignature
call 메소드로 다른 컨트랙트의 기능을 호출할 때 사용하며, 인자값으로 함수 서명과 함수 서명의 매개변수들이 차례로 들어갑니다. abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)와 동일합니다.
interface IERC20 {
function transfer(address, uint) external;
}
function encodeWithSignature(address to, uint amount) external pure returns (bytes memory) {
return abi.encodeWithSignature("transfer(addres,uint256)", to, amount);
}
3️⃣ ABI encodeCall
abi.encodeWithSelector와 유사하지만 제공된 값이 호출된 함수의 타입과 일치하는지 확인합니다.
interface IERC20 {
function transfer(address, uint) external;
}
function encodeCall(address to, uint amount) external pure returns (bytes memory) {
return abi.encodeCall(IERC20.transfer, (to, amount));
}
❗️ function signature
함수 서명은 함수와 함수가 사용하는 매개변수 타입의 조합입니다.
function transfer(address to, uint256 amount) public {
// CODE
}
// function signature
transfer(address, uint256)
❗️ function selector
- 함수 선택자는 호출할 함수를 지정하는 함수 호출 데이터의 처음 4Byte입니다.
- EVM에서 스마트 컨트랙트를 실행한다고 하면 어떤 기능을 실행하는지 알려주는 역할입니다.
- 함수 선택자는 동일한 함수 서명의 해시입니다.
bytes4(keccak256(bytes(function_signature)))
❗️ Call, delegate Call, static Call
1️⃣ call
외부 컨트랙트를 호출하는 기본적인 메서드로 A컨트랙트에서 B컨트랙트에 접근해 storage에 저장된 데이터를 변경할 수 있습니다. 이때 msg.sender는 A 컨트랙트의 주소가 됩니다.
2️⃣ delegate call
call 메서드와 비슷한데, 차이점은 A 지갑에서 A 컨트랙트를 이용해 B 컨트랙트를 호출할 때 msg.sender는 A 컨트랙트 주소가 아닌 A 지갑주소가 됩니다. 또한 데이터를 변경한다고 했을 때, B 컨트랙트의 데이터가 바뀌는 것이 아닌 A 컨트랙트의 데이터만 변경됩니다.
3️⃣ static call
함수 내에서 상태값을 변경하지 않는 pure 함수와 view 함수는 static call로 호출합니다.
댓글