[멋쟁이 사자처럼 블록체인 스쿨 3기] 23-06-13

임형석·2023년 6월 13일
0

Ethers.js

ethers.js 는 web3.js 처럼 이더리움 네트워크를 연결할 수 있는 라이브러리이다.

이더리움 네트워크에 연결하여, 트랜잭션 데이터나 컨트랙트 데이터를 가져오거나, 함수를 실행하는 등의 조작을 할 수 있다.


Ethers.js 사용

ethers.js 를 이용하여 다른 지갑으로 이더를 전송해보았다.

먼저 터미널을 켜고 사용할 폴더로 이동한다.

npm install --save ethers => ethers.js 설치.

var {ethers} = require('ethers')
var provider_main = new ethers.InfuraProvider() => infura 로 지정. API 키를 받아서 변수를 선언할 필요가 없음.

var provider_goerli = new ethers.InfuraProvider(network="goerli") => goerli 테스트 넷으로 네트워크를 설정.
await provider_main.getBlockNumber()
await provider_goerli.getBlockNumber() => 네트워크가 잘 지정되었는지 블록 넘버를 불러와 확인.

var privateKey = '0x privatekey' => 지갑을 불러오기 위해 개인키 설정.
var signer = new ethers.Wallet(privateKey, provider_goerli) => 서명.

var account2 = '0x00554f76D41B5302279dC6b7eAEe8A37e2e89aa1' => 보낼주소 지정.
var tx = {to : account2, value : 100000000000000} => 트랜잭션 생성.

signer.sendTransaction(tx).then(console.log) => 트랜잭션 전송.


컨트랙트 함수를 실행하여 상태변수를 변경시켜보았다.

var abi = [CA ABI] => 컨트랙트 abi 설정.
var c_addr = 'CA' => CA 설정.
var contract = new ethers.Contract(c_addr, abi, signer) => 컨트랙트 지정.


Solidity


tx.origin

이더리움에는 tx.origin 이라는 전역변수가 있다. 이 변수는 어떠한 함수를 call 한 주소를 기록한다.

그렇기에, 1번 주소가 1번 컨트랙트를 통해서 2번 컨트랙트의 함수를 호출해도 1번 주소를 기록한다.

그럼 아래와 같이 컨트랙트를 작성하고 확인해보자.

contract A {
    function a() public view returns(address){
        return msg.sender;
    }

    function b() public view returns(address){
        return address(this);
    }

    function c() public view returns(address){
        return tx.origin;
    }
}

contract B {
    A c_a;

    constructor(address _c){
        c_a = A(_c);
    }

    function a() public view returns(address){
        return c_a.a();
    }

    function b() public view returns(address){
        return c_a.b();
    }

    function c() public view returns(address){
        return c_a.c();
    }
}

위 컨트랙트를 배포하고 실행하면 아래와 같이 나온다.

B 컨트랙트로 실행한 a 는 당연히 B 컨트랙트의 주소가 나올 것이다.

하지만 B 컨트랙트의 c 함수는 B 컨트랙트의 주소 대신 내 지갑 주소를 반환했다.

tx.origin 을 통해 처음으로 함수를 call 한 주소를 불러냈기 때문이다.


tx.origin 을 응용하여 사용할 수 있다.

require(msg.sender == tx.origin) 이라는 조건을 함수 내에 걸어둔다면?

다른 상속받은 컨트랙트에서도 함수에 접근할 수 없을 것이다.

이 외에는 tx.origin 을 이용한 조건은 걸어두지 않는 것이 좋다.

다른 컨트랙트에서 접근하면 위조할 수 있기 때문이다.


Multi Array

이중 배열을 사용해 2차원으로 값을 저장할 수 있다.

2차원으로 저장한다는 의미는 곧 하늘에서 내려다 보는, 엑셀처럼 저장한다고 생각하면 쉽게 이해할 수 있겠다.

그래서 때로는, 2차원 배열을 이용해 자료를 저장하는 것이 효율적일 수도 있다.

아래의 컨트랙트를 작성하고 실험을 해보았다.

contract multiArray {
    uint[][] public double_A;

    function setNumber(uint _a) public {
        double_A.push([_a]);
    }

    function setNumber2(uint _a, uint _b) public {
        double_A.push([_a, _b]);
    }

    function setNumber3(uint[] memory _a) public {
        double_A.push(_a);
    }

    function setNumber4(uint _a, uint _b, uint _c) public {
        double_A[_a][_b] = _c;
    }

    function getLength() public view returns(uint) {
        return double_A.length;
    }

    function getLength2(uint _a) public view returns(uint) {
        return double_A[_a].length;
    }
}

setNumber() 함수에 0~3 까지의 숫자를 넣고 length 와 2차원 배열 A 를 확인해보았다.

즉, push 를 사용하여 값을 한 가지만 입력하면 x 값만 바뀌게 된다. A[x][y] 형태로 배열이 저장된다고 생각하면 된다.

현재 A 배열에는 값이 아래와 같이 저장되어 있다고 보면 이해하기 쉽다.


다음은 setNumber2() 함수에 1,1 .. 11,11 .. 111,111 .. 1111,1111 숫자를 차례대로 넣어 실행해보았다.

아래와 같이 저장되어 있다고 볼 수 있다.


다음은 setNumber3 함수를 실행해보았다. [0,0], [0,1][0,2] [3] 순서로 실행했다.

아래와 같이 저장되어 있다고 볼 수 있다.

위의 함수는 x 배열에 차례대로 저장하는데,

배열의 숫자가 하나일 경우 y[0] 위치에.

배열의 숫자가 두개일 경우 y[0,1] 위치에 각각 저장하게 된다.


다음은 setNumber4 함수에 0,0,33 인풋값과 함께 함수를 실행해보았다. 하지만 실행이 불가능하다.

왜? setNumber4 함수는 위의 1~3 함수와 다르게 push 를 사용하지 않는다.

push 는 동적배열에 사용이 가능하다.

그리고, 아래와 같이 배열의 키와 밸류를 직접 지정하는 것은 정적인 동작이다.

double_A[_a][_b] = _c;

그래서, push 를 이용해서 미리 해당 배열에 값을 저장해놓아야 4번째 함수를 실행가능하고, 4번째 함수는 해당하는 배열에 있던 값을 수정하는 것이다.

그래서 먼저, setNumber() 함수를 이용해서 0~3 까지 총 4개의 값을 저장했다.

그리고 setNumber4() 함수로 A[2][0] 배열에 255 라는 값을 저장했다.

결과는 아래와 같다.

2,0 배열에 255라는 값이 저장되었고, 배열의 총 길이는 4이다.

아래와 같이 저장되었다고 볼 수 있다.


다음은 배열 뒷부분의 길이를 지정한 2차원 배열에 값을 저장해보았다.

	uint[][3] public double_A;
    
    function setNumberA2(uint _a, uint[] memory _b) public {
        double_A[_a] = _b;
    }

여기서 setNumberA2 함수를 1,[2,3] 으로 실행시키면 어떻게 될까?

x 를 기준으로 값이 저장된다. 아래와 같이 저장되었다고 볼 수 있다.

인풋을 배열로 만들어 함수를 실행한다면, 아래 처럼 x 를 기준으로 y 배열에 하나씩 값이 저장된다.


그렇다면, 이차원 배열의 길이가 모두 지정되어 있다면?

완벽하게 정적인 상태라면 원하는 배열에 값을 저장할 수 있을까?

아래와 같이 컨트랙트를 수정하고 확인해보았다.

	uint[3][3] public double_A;

    function setNumberA(uint _a, uint _b, uint _c) public {
        double_A[_a][_b] = _c;
    }

setNumberA 함수에 0,0,0 | 1,1,1 | 2,2,2 를 순서대로 실행시켰다.

값은 아래와 같이 저장되었다고 볼 수 있다.

이차원 배열 모두가 완벽하게 정적이라면, 정확히 원하는 배열에 값을 저장할 수 있다.

그렇지 않다면, push 함수를 우선 사용하여 값을 넣은 뒤 위의 함수를 사용하여 원하는 배열을 수정하는 방법을 쓸 수 있겠다.

이차원 배열을 응용한다면, 각 학생의 각 과목 점수를 관리할 때 사용할 수도 있겠다..


Multi Array 가스비 비교

두개의 배열의 길이를 정해 정적으로 만든 이차원 배열 _B 가 가장 적은 가스비를 소모한다.

하지만, setB2 함수와 같이 상황에 따라 가스비가 덜 소모되는 경우도 있을 것이다.

0개의 댓글