url: https://solidity-by-example.org/structs/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Todos {
struct Todo {
string text;
bool completed;
}
// An array of 'Todo' structs
Todo[] public todos;
function create(string calldata _text) public {
// 3 ways to initialize a struct
// - calling it like a function
todos.push(Todo(_text, false));
// key value mapping
todos.push(Todo({text: _text, completed: false}));
// initialize an empty struct and then update it
Todo memory todo;
todo.text = _text;
// todo.completed initialized to false
todos.push(todo);
}
// Solidity automatically created a getter for 'todos' so
// you don't actually need this function.
function get(uint _index) public view returns (string memory text, bool completed) {
Todo storage todo = todos[_index];
return (todo.text, todo.completed);
}
// update text
function updateText(uint _index, string calldata _text) public {
Todo storage todo = todos[_index];
todo.text = _text;
}
// update completed
function toggleCompleted(uint _index) public {
Todo storage todo = todos[_index];
todo.completed = !todo.completed;
}
}
Construct 키워드를 통해 사용자 정의 자료형을 그룹화하여 사용할 수 있다.
아래와 같이 Todo 구조체 배열에 값을 추가할 수 있다.
// Todo(_text, false) 자동 생성자로 생성 후 push
todos.push(Todo(_text, false));
// key value mapping
todos.push(Todo({text: _text, completed: false}));
// 빈 구조체 변수를 생성하여 값을 저장한 후 push
Todo memory todo;
todo.text = _text;
// todo.completed initialized to false
todos.push(todo);
일반적으로, Solidity는 구조체를 선언하면 기본 생성자와 getter 함수가 자동 생성된다.
url: https://solidity-by-example.org/data-locations/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract DataLocations {
uint[] public arr;
mapping(uint => address) map;
struct MyStruct {
uint foo;
}
mapping(uint => MyStruct) myStructs;
function f() public {
// call _f with state variables
_f(arr, map, myStructs[1]);
// get a struct from a mapping
MyStruct storage myStruct = myStructs[1];
// create a struct in memory
MyStruct memory myMemStruct = MyStruct(0);
}
function _f(
uint[] storage _arr,
mapping(uint => address) storage _map,
MyStruct storage _myStruct
) internal {
// do something with storage variables
}
// You can return memory variables
function g(uint[] memory _arr) public returns (uint[] memory) {
// do something with memory array
}
function h(uint[] calldata _arr) external {
// do something with calldata array
}
}
Solidity에서는 데이터의 저장 위치를 명시적으로 선언하기 위해 storage, memory, calldata를 사용할 수 있다. Data Location은 Array, special array로 Array 취급을 받는 String과 bytes, Struct, Mapping 타입 (Reference Type)에서만 사용할 수 있다.
function _f(
string storage c
) internal {
// do something with storage variables
}
but, 함수의 접근제어자가 public이면 string c 참조 변수에 data location을 storage가 아닌, memory, calldata 로만 선언할 수 있다. why? 외부에서 블록체인 상태를 변경할 수 있는 보안 문제가 발생할 수 있음에 따라 public, external 접근 제어자에서는 storage 데이터 위치를 사용할 수 없도록 제한되어 있다.
contract jhcha{
mapping(uint => string) myMapping;
function _f(string memory c) internal{
// mapping과 같은 복합 데이터 타입을 storage data location으로 생성 불가.
// 상태 변수로 선언되어야 함.
// mapping(uint => string) errMapping;
}
}
memory와 calldata의 차이는 값을 수정할 수 있는지 여부, calldata는 함수의 return으로 불가능하다. memory는 가장 먼저 데이터가 calldata 형태로 입력받고, memory를 만들어서 calldata의 데이터를 복사하여 임시 저장한다.
(참고자료: https://wnjoon.github.io/2022/10/21/blockchain-eth_data_location/)
function f() public {
// call _f with state variables
_f(arr, map, myStructs[1]);
}
function _f(
uint[] storage _arr,
mapping(uint => address) storage _map,
MyStruct storage _myStruct
) internal {
// do something with storage variables
}
참조 타입인 array, map, struct는 f()함수를 호출함으로써 상태 변수가 _f() 함수에서 수정된다.
function f() public {
_f(myStructs[1]);
get(myStructs[1]);
}
function _f(
MyStruct storage _myStruct
) internal {
_myStruct.foo = 123;
}
따라서, 위와 같은 예시에서 참조 타입 struct를 상태 변수 상태에 접근하여 데이터를 수정할 수 있다.
url: https://solidity-by-example.org/function/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Function {
// Functions can return multiple values.
function returnMany() public pure returns (uint, bool, uint) {
return (1, true, 2);
}
// Return values can be named.
function named() public pure returns (uint x, bool b, uint y) {
return (1, true, 2);
}
// Return values can be assigned to their name.
// In this case the return statement can be omitted.
function assigned() public pure returns (uint x, bool b, uint y) {
x = 1;
b = true;
y = 2;
}
// Use destructuring assignment when calling another
// function that returns multiple values.
function destructuringAssignments()
public
pure
returns (uint, bool, uint, uint, uint)
{
(uint i, bool b, uint j) = returnMany();
// Values can be left out.
(uint x, , uint y) = (4, 5, 6);
return (i, b, j, x, y);
}
// Cannot use map for either input or output
// Can use array for input
function arrayInput(uint[] memory _arr) public {}
// Can use array for output
uint[] public arr;
function arrayOutput() public view returns (uint[] memory) {
return arr;
}
}
// Call function with key-value inputs
contract XYZ {
function someFuncWithManyInputs(
uint x,
uint y,
uint z,
address a,
bool b,
string memory c
) public pure returns (uint) {}
function callFunc() external pure returns (uint) {
return someFuncWithManyInputs(1, 2, 3, address(0), true, "c");
}
function callFuncWithKeyValue() external pure returns (uint) {
return
someFuncWithManyInputs({a: address(0), b: true, c: "c", x: 1, y: 2, z: 3});
}
}
해당 예제는 함수 return의 여러가지 방식에 대해서 다루고 있다.
함수 return에 자료형을 명시하는 경우
// Functions can return multiple values.
function returnMany() public pure returns (uint, bool, uint) {
return (1, true, 2);
}
함수 return에 memory 타입의 지역 변수로 내보내는 경우 (트랜잭션 output에 변수 명이 기재된다.)
// Return values can be named.
function named() public pure returns (uint x, bool b, uint y) {
return (1, true, 2);
}
함수 return에 memory 타입의 지역 변수로 내보내는 경우, 함수 내부에 반환문을 생략할 수 있다.
// Return values can be assigned to their name.
// In this case the return statement can be omitted.
function assigned() public pure returns (uint x, bool b, uint y) {
x = 1;
b = true;
y = 2;
}
다중 변수에 값을 대입하고, (x, ,y) = (4, 5, 6)인 경우, 5를 생략하고 4와 6이 저장된다.
function destructuringAssignments()
public
pure
returns (uint, bool, uint, uint, uint)
{
(uint i, bool b, uint j) = returnMany();
// Values can be left out.
(uint x, , uint y) = (4, 5, 6);
return (i, b, j, x, y);
}
arrayInput 함수를 통해 아래와 같이 arr에 값을 추가할 수 있다. ex. arrayInput([10, 20, 30])
uint[] public arr;
function arrayInput(uint[] memory _arr) public {
//_arr.push(1);
for(uint i=0; i<_arr.length; i++){
arr.push(_arr[i]);
}
}
memory 타입의 배열은 동적으로 크기가 변할 수 없으므로 _arr.push(1)에서 에러가 발생한다.
url: https://solidity-by-example.org/view-and-pure-functions/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ViewAndPure {
uint public x = 1;
// Promise not to modify the state.
function addToX(uint y) public view returns (uint) {
return x + y;
}
// Promise not to modify or read from the state.
function add(uint i, uint j) public pure returns (uint) {
return i + j;
}
}
Getter 함수는 함수 제어자 (function modifier) view나 pure 를 사용할 수 있다.
function addToX(uint y) public view returns (uint) {
// x = 1; 상태 변수를 수정하지 못한다.
return x + y;
}
function addToX(uint y) public view returns (uint) {
// x = 1; 상태 변수를 읽거나 수정하지 못한다.
return x + y;
}