본문 바로가기
  • 인공지능
  • 블록체인
  • 정보보안
신기술분석/블록체인

블록체인 Dapp 만들기 #9

by nathan03 2021. 7. 15.
반응형

# 스마트 계약 적용 가능 사례 

부동산 시나리오 (예) :
    1. 부동산 중계인을 통해 매수의사 전달 
    2. 계약 요청 
    3. 상호 동의 
    4. 계약 완료 
    5. 대금 지불 
    6. 명의 이전 

단계별 블록체인에 영구 저장 (한명이라도, 중간에서 조작 불가, 또한 명의 이전 시 거래 내역이 한번에 나옴)

궁극적으로 이러한 시스템을 만든다면, 공인 중개사는 사라질수 있을 것이다. 

# 주의할점 
애플리케이션의 모든 정보를 블록체인에 저장하면 안된다. (꼭 필요한 내용만 저장, 기타 부분은 일반적인 DB에 저장)
예) 부동산 매물 리스트는 Json 형태로 불러오는 것임 (부동산 매물이 많다면, 매물을 매번 블록체인에 올리면 수수료 비용이 발생하고 Transaction 처리에 따른 문제가 발생할수 있다.)
---> 아이디와 매입자 정보만 블록체인에 저장하자. 

 

웹개발에 맞는 템플릿 다운 받기 

https://www.trufflesuite.com/boxes

 

Boxes | Truffle Suite

Truffle Boxes contain helpful modules for dapp development like, Solidity contracts & libraries, front-end views all the way up to complete dapp examples and templates.

www.trufflesuite.com

 

https://github.com/kkagill/real-estate-starter

 

kkagill/real-estate-starter

Contribute to kkagill/real-estate-starter development by creating an account on GitHub.

github.com

 

1. 컨트랙트 소유자 설정하기 

pragma solidity ^0.4.23;

contract RealEstate {
    address public owner;

    constructor() publc {
        owner = msg.sender;
    }
}

 

2. 배포하기 (truffle migrate --network ganache)

가나쉬의 첫번째 계정이 배포하는데 사용됨 (BALANCE가 줄어든것을 확인)

 

컨트랙트의 소유자 첫번째 계정 확인

어떤 사람이 매물을 매입했을때 매입가를 Owner 계정으로 송금 
가나슈 첫번째 계정으로 배포를 하였고, 그 계정으로 RealEstate Contract의 생성자로 호출해서 Contract의 owner 소유자를 가나슈의 첫번째 계정으로 설정하였다. 

3. 첫 테스팅 
블록체인의 스마트컨트랙트를 한번 배포하고 나오면 배포한 주소에서는 수정하는게 불가능하기 때문에 최대한 많은 테스트를 하고 메인넷에 배포해야한다. 

TestRealEstate.js

var RealEstate = artifacts.require("./RealEstate.sol");

contract('RealEstate', function(accounts) {
    var RealEstateInstance;

    it("컨트랙의 소유자 초기화 테스팅", function() {
        return RealEstate.deployed().then(function(instance) {
            realEstateInstance = instance;
            return realEstateInstance.owner.call();
        }).then(function(owner) {
            assert.equal(owner.toUpperCase(), accounts[0].toUpperCase(), "owner가 가나슈 첫번째 계정과 동일하지 않습니다.");
        });
    });
});

 truffle test --network ganache 

1 failing 첫번째 케이스 실패  (instance 오타 ^^;;) 

1 passing 이 뜨면서 첫번째 케이스 통과 (owner 가 가나슈 첫번째 계정과 일치함을 확인)

# 한국어 깨질시 chcp 949 입력

첫번째 계정이 아닌 두번째 계정으로 테스트 시 
assert.equal(owner.toUpperCase(), accounts[1].toUpperCase(), "owner가 가나슈 첫번째 계정과 동일하지 않습니다.");

owner가 가나슈 첫번째 계정과 동일하지 않습니다. 라는 문구 출력 

매물 구입 함수 구현

pragma solidity ^0.4.23;

contract RealEstate {
    struct Buyer {
        address buyerAddress;
        bytes32 name;
        uint age;
    }
    
    mapping (uint => Buyer) public buyerInfo;
    address public owner;
    address[10] public buyers;

    constructor() public {
        owner = msg.sender;
    }

    function buyRealEstate(uint _id, bytes32 _name, uint _age) public payable {
        require(_id > 0 && _id <= 9); //유효성 체크 
        buyers[_id] = msg.sender;
        buyerInfo[_id] = Buyer(msg.sender, _name, _age);

        owner.transfer(msg.value);
    }
}

컨트랙트 재컴파일 및 배포 (truffle migrate --compile-all --reset --network ganache)

트러플 콘솔을 열고 app의 buyRealEstate()함수를 실행해보자. 

RealEstate.deployed().then(function(instance) { app = instance;})
app.buyRealEstate(0, "sh" , 24, {from: web3.eth.accounts[1], value : web3.toWei(1.50,"ether")})

결과는 실패 !  Error: VM Exception while processing transaction: revert 
휴 ...어렵구먼, 원인은 require(_id >0 && _id <= 9);  id 로직 체크 부분에 _id>=0 을 잘못 넣음 (재배포)

두번째 계정에, 1.50 + 수수료가 차감된걸 볼 수 있음

이벤트 추가 

매물이 매입이 완료되면, 매입이 완료 됐다는것을 이벤트를 통해 알려주자. 

pragma solidity ^0.4.23;

contract RealEstate {
    struct Buyer {
        address buyerAddress;
        bytes32 name;
        uint age;
    }
    
    mapping (uint => Buyer) public buyerInfo;
    address public owner;
    address[10] public buyers;

    // 이벤트
    event LogBuyRealEsate(
        address _buyer,
        uint _id
    );

    constructor() public {
        owner = msg.sender;
    }

    function buyRealEstate(uint _id, bytes32 _name, uint _age) public payable {
        require(_id >=0 && _id <= 9); //유효성 체크 
        buyers[_id] = msg.sender;
        buyerInfo[_id] = Buyer(msg.sender, _name, _age);

        owner.transfer(msg.value);
        emit LogBuyRealEsate(msg.sender, _id); // 매입자와 매물의 ID를 넘겨서 이벤트 발생
    }
}

이벤트 추가 메시지 확인

매물을 추가해서 이벤트 확인 

# 읽기 전용 함수들 

pragma solidity ^0.4.23;

contract RealEstate {
    struct Buyer {
        address buyerAddress;
        bytes32 name;
        uint age;
    }
    
    mapping (uint => Buyer) public buyerInfo;
    address public owner;
    address[10] public buyers;

    // 이벤트
    event LogBuyRealEstate(
        address _buyer,
        uint _id
    );

    constructor() public {
        owner = msg.sender;
    }

    function buyRealEstate(uint _id, bytes32 _name, uint _age) public payable {
        require(_id >=0 && _id <= 9); //유효성 체크 
        buyers[_id] = msg.sender;
        buyerInfo[_id] = Buyer(msg.sender, _name, _age);

        owner.transfer(msg.value);
        emit LogBuyRealEstate(msg.sender, _id); // 매입자와 매물의 ID를 넘겨서 이벤트 발생
    }

    // 데이터를 불러오는 읽기 전용 함수들 
    function getBuyerInfo(uint _id) public view returns (address, bytes32, uint) {
        Buyer memory buyer = buyerInfo[_id];
        return (buyer.buyerAddress, buyer.name, buyer.age);
    }

    function getAllBuyer() public view returns (address[10]) {
        return buyers;
    }
}

 

재컴파일 및 가나쉬 배포 

매물을 하나 매입하기 (매입자이름, 나이, 어느계정에서 불러오는지 명시(두번째 계정), 1.5ether 송금) 

매입자의 정보를 불러오기 

매입자의 계정을 저장하는 배열을 불러오기 

읽기 전용 함수들은 가스비를 내지 않는다.

 

마무리 테스팅

var RealEstate = artifacts.require("./RealEstate.sol");

contract('RealEstate', function(accounts) {
    var RealEstateInstance;

    it("컨트랙의 소유자 초기화 테스팅", function() {
        return RealEstate.deployed().then(function(instance) {
            realEstateInstance = instance;
            return realEstateInstance.owner.call();
        }).then(function(owner) {
            assert.equal(owner.toUpperCase(), accounts[0].toUpperCase(), "owner가 가나슈 첫번째 계정과 동일하지 않습니다.");
        });
    });

    it("가나슈 두번째 계정으로 매물 아이디 0번 매입 후 이벤트 생성 및 매입자 정보와 buyers 배열 테스팅", function() {
        return RealEstate.deployed().then(function(instance) {
            realEstateInstance = instance;
            return realEstateInstance.buyRealEstate(0, "sh", 13, {form: accounts[1], value: web3.toWei(1.50, "ether")})
        }).then(function(receipt) {
            assert.equal(receipt.logs.length, 1, "이벤트 하나가 생성되지 않았습니다.");
            assert.equal(receipt.logs[0].event, "LogBuyRealEstate", "이벤트가 LogBuyRealEstate가 아닙니다.");
            assert.equal(receipt.logs[0].args._buyer, accounts[1], "매입자가 가나슈 두번째 계정이 아닙니다.");
            assert.equal(receipt.logs[0].args._id, 0, "매물 아이디가 0이 아닙니다.");
            return realEstateInstance.getBuyerInfo(0);
        }).then(function(buyerInfo) {
            assert.equal(buyerInfo[0].toUpperCase(), accounts[1].toUpperCase(), "매입자의 계정이 가나슈 두번째 계정과 일치하지 않습니다.");
            assert.eqaul(web3.toAscii(buyerInfo[1]).replace(/\0/g, ''), "sh", "매입자의 이름이 sh가 아닙니다.");
            assert.equal(buyerInfo[2], 13, "매입자의 나이가 13살이 아닙니다.");
            return realEstateInstance.getAllBuyers();
        }).then(function(buyers) {
            assert.equal(buyers[0].toUpperCase(), account[1].toUpperCase(), "Buyers 배열 첫번째 인덱스의 계정이 가나슈 두번째 계정과 일치하지 않습니다.")
        });
    })
});

 

반응형

댓글