ethers.js 는 web3.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)
=> 컨트랙트 지정.
이더리움에는 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
을 이용한 조건은 걸어두지 않는 것이 좋다.
다른 컨트랙트에서 접근하면 위조할 수 있기 때문이다.
이중 배열을 사용해 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 함수를 우선 사용하여 값을 넣은 뒤 위의 함수를 사용하여 원하는 배열을 수정하는 방법을 쓸 수 있겠다.
이차원 배열을 응용한다면, 각 학생의 각 과목 점수를 관리할 때 사용할 수도 있겠다..
두개의 배열의 길이를 정해 정적으로 만든 이차원 배열 _B 가 가장 적은 가스비를 소모한다.
하지만, setB2 함수와 같이 상황에 따라 가스비가 덜 소모되는 경우도 있을 것이다.