[Solidity] Structs, Data Locations, Function, View, Pure

jhcha·2023년 7월 28일
0

Solidity

목록 보기
5/17
post-thumbnail

Structs

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 함수가 자동 생성된다.

Data Locations - Storage, Memory and Calldata

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 데이터 위치를 사용할 수 없도록 제한되어 있다.

  • storage - 상태 변수와 동일한 의미로 블록체인에 저장되어 영속성을 갖는다.
  • memory - 변수가 메모리에 존재하며 함수가 호출되는 동안에만 존재한다.
  • calldata - 트랜잭션의 data field을 input data 혹은 calldata 라고 한다. calldata는 함수 호출 시 입력값에 사용되고 memory와 같이 임시 저장되는 변수지만 수정이 불가능하다.
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를 상태 변수 상태에 접근하여 데이터를 수정할 수 있다.

Function

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)에서 에러가 발생한다.

View and Pure Functions

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 를 사용할 수 있다.

  • view는 함수 내부에서 어떤 상태도 변경하지 못하도록 한다.
    function addToX(uint y) public view returns (uint) {
        // x = 1; 상태 변수를 수정하지 못한다.
        return x + y;
    }
  • pure는 상태 변수를 읽거나 수정하지 못한다. 따라서, 함수 내부 상태만 변경할 수 있다.
    function addToX(uint y) public view returns (uint) {
        // x = 1; 상태 변수를 읽거나 수정하지 못한다. 
        return x + y;
    }

0개의 댓글