Skip to content

About

Smart Contract code of decentralized demand-response bus platform.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DBUS Contract

"이 동네에 버스 노선 하나 생겼으면 좋겠다" 를 블록체인으로 실현하는 프로젝트입니다.

주민들이 원하는 노선에 토큰을 모으고, 목표 금액을 넘기면 그 돈이 운영 주체에게 전달되어 노선이 개설됩니다. 그리고 돈을 낸 사람은 낸 만큼 그 노선의 탑승권(NFT) 을 받습니다.

이 저장소는 그중 스마트 컨트랙트(블록체인에 올라가는 코드) 부분입니다.


1. 어떻게 동작하나요?

  ①  운영자가 노선을 등록합니다
      "3번 노선 / 목표 10,000 토큰 / 돈 받을 곳: OO버스회사"
                        │
                        ▼
  ②  주민들이 펀딩합니다
      낸 토큰은 일단 컨트랙트가 금고처럼 보관합니다 (아직 아무도 못 가져감)
                        │
                        ▼
  ③  모금액이 목표를 넘으면? ──── 자동으로 아래 2가지가 한 번에 실행됩니다
      │
      ├─ 모인 토큰 전액을 버스회사에게 전송  →  노선 개설 확정!
      └─ 참여자들에게 탑승권 NFT를 기여한 비율대로 나눠줌
                        │
                        ▼
  ④  버스를 탈 때 탑승권 NFT를 사용(반납)합니다

핵심은 "목표를 넘기면 사람 손 안 거치고 자동으로 정산된다" 입니다. 중간에서 누가 돈을 들고 튈 수 없고, 목표를 못 넘기면 돈은 계속 금고에 남아있습니다.


2. 토큰이 2종류입니다

ERC-1155라는 표준을 쓰면 한 개의 컨트랙트 안에서 여러 종류의 토큰을 번호(id)로 구분해 만들 수 있습니다. DBUS는 이 점을 이용해 아래처럼 나눴습니다.

토큰 번호 이름 성격 설명
0 포인트 토큰 서로 구분 없음 (화폐처럼) 펀딩할 때 내는 돈
1, 2, 3 ... 탑승권 NFT 노선마다 다름 번호 = 노선 번호. 1번 노선 탑승권, 2번 노선 탑승권 ...

즉 토큰 id가 곧 노선 id입니다. 새 노선이 생기면 새 종류의 탑승권이 자동으로 생깁니다.

탑승권의 이미지·노선 정보 같은 데이터는 블록체인에 직접 넣으면 비싸기 때문에, IPFS(분산 파일 저장소)에 올려두고 컨트랙트는 그 주소만 가리킵니다.


3. 파일 구조

contracts/
├── DTicket.sol           # 토큰 컨트랙트 (포인트 + 탑승권 NFT)
├── FundRegistry.sol      # 핵심 로직 (펀딩 등록 / 모금 / 자동 정산 / 티켓 분배)
├── *_flattened.sol       # Etherscan 코드 인증용으로 한 파일에 합쳐놓은 버전
scripts/
├── deploy_with_ethers.ts # 배포 스크립트 (ethers.js)
├── deploy_with_web3.ts   # 배포 스크립트 (web3.js)
├── ethers-lib.ts         # 배포 공통 함수
├── web3-lib.ts           # 배포 공통 함수
└── bulk_mint_nfts.js     # NFT를 100개씩 끊어서 한 번에 발행하는 스크립트
tests/
└── MyToken_test.sol      # 초기 ERC-20 버전의 테스트 (현재는 사용하지 않음)
.deps/                    # OpenZeppelin 라이브러리 (Remix가 자동으로 받아온 것)

개발 환경은 Remix IDE 기준입니다. (Hardhat / Foundry 아님)


4. 컨트랙트 상세

DTicket.sol — 토큰 담당

함수 하는 일
mint(계정, 토큰번호, 수량) 토큰을 새로 찍어냄 (운영자만)
mintBatch(...) 여러 종류를 한 번에 찍어냄 (가스비 절약)
uri(토큰번호) 해당 토큰의 정보가 담긴 IPFS 주소를 돌려줌
setURI(주소) 메타데이터 위치 변경 (운영자만)

FundRegistry.sol — 펀딩 담당 (메인)

노선 하나가 Fund라는 구조체 하나입니다.

struct Fund {
    uint96  id;              // 노선 번호
    address owner;           // 이 노선 정보를 수정할 수 있는 사람
    address payee;           // 모금이 성공하면 돈을 받을 주소
    uint256 threshold;       // 목표 금액
    uint256 donationAmount;  // 지금까지 모인 금액
    bool    isEnd;           // 모금 완료 여부
    uint48  createdAt;       // 생성 시각
    uint48  updatedAt;       // 수정 시각
}
함수 하는 일
createFund(owner, payee, 목표액) 새 노선 펀딩을 엽니다
donate(유저, 노선번호, 금액) 펀딩 참여. 토큰을 금고에 넣고 기록한 뒤, 목표 달성 여부를 즉시 검사합니다
updateFund(...) 노선 정보 수정. 그 노선을 만든 사람만 가능합니다
burn(유저, 수량, 노선번호) 탑승 시 탑승권을 회수합니다
getAllFunds() / getFunds(시작, 끝) 노선 목록 조회
defaultMintToOwner(수량) 운영자에게 포인트 토큰을 발행

내부에서만 도는 함수들:

  • createDonation() — 토큰을 금고로 옮기고 기여 내역을 저장
  • validateFunds() — 목표를 넘었는지 확인하고, 넘었으면 정산 + 티켓 발행까지 처리
  • mintDTiket() — 참여자별 기여 비율대로 탑승권 50장을 나눠줌 (예: 총 1,000 토큰이 모였고 내가 200을 냈다면 → 50 × 200 / 1000 = 10장)

이벤트 (앱/서버가 상태를 추적하는 용도)

블록체인은 "조회"가 느리고 비싸기 때문에, 변화가 생길 때마다 이벤트를 쏴서 서버나 앱이 그걸 받아 화면에 보여주는 구조를 씁니다.

이벤트 언제 발생
FundCreated 새 노선 펀딩이 열렸을 때
FundUpdated 노선 정보가 수정됐을 때
FundUserAdded 새로운 참여자가 처음 펀딩했을 때
FundCompletion 목표 달성 → 노선 개설 확정!

5. 배포 순서 (중요)

순서를 지키지 않으면 티켓 자동 발행이 동작하지 않습니다.

1. DTicket 배포                       (생성자 인자: 운영자 주소)
2. FundRegistry 배포                  (생성자 인자: DTicket 주소, 운영자 주소)
3. DTicket.transferOwnership(FundRegistry 주소)
     └ FundRegistry가 티켓을 발행할 권한을 가져야 하기 때문
4. 유저는 DTicket.setApprovalForAll(FundRegistry 주소, true)
     └ 내 토큰을 펀딩에 쓸 수 있도록 미리 허락해두는 것

6. 설계하면서 바꾼 것들

초기 버전에서 두 번 크게 방향을 바꿨습니다.

① Quadratic Funding → 목표 금액(threshold) 방식

처음에는 "소수의 큰손보다 다수의 소액 기여를 더 쳐주는" 이차 펀딩(Quadratic Funding) 공식 (∑√기여액)² 을 컨트랙트 안에 직접 구현했습니다. 솔리디티에는 소수점과 제곱근이 없어서 제곱근 함수도 직접 만들었습니다.

하지만 이 계산은 매번 전체 노선 × 전체 참여자 × 전체 기여내역을 3중으로 훑어야 해서 참여자가 늘어날수록 수수료가 감당이 안 되는 구조였습니다. 그래서 "목표 금액을 넘으면 정산" 이라는 단순하고 저렴한 방식으로 교체했습니다.

② ERC-20 + ERC-721 → ERC-1155 하나로 통합

처음엔 화폐용 토큰(ERC-20)과 탑승권을 따로 만들려 했지만, ERC-1155 하나로 두 역할을 모두 처리할 수 있어서 컨트랙트를 하나로 합쳤습니다. 배포 비용도 줄고, 토큰끼리 주고받는 로직도 단순해졌습니다.


7. 알려진 한계

솔직하게 적어둡니다. 개선 예정 항목입니다.

  • 운영자 권한이 큽니다. donate를 포함한 대부분의 함수가 운영자만 호출할 수 있습니다. (유저 대신 서버가 트랜잭션을 대신 보내주는 구조라 유저가 가스비를 안 내도 되는 장점은 있습니다)
  • 참여자가 아주 많아지면 정산이 실패할 수 있습니다. 티켓을 나눠줄 때 참여자 수만큼 반복문을 돌기 때문에, 각자 직접 받아가는(claim) 방식으로 바꾸는 게 안전합니다.
  • 티켓 분배 시 나머지가 버려집니다. 정수 나눗셈이라 아주 적게 기여한 사람은 0장을 받을 수 있습니다.
  • 목표액에 딱 맞게 도달하면 완료되지 않습니다. 비교 조건이 초과로 되어 있습니다.
  • burn() 함수가 잔액을 확인할 때와 실제로 옮길 때의 토큰 번호가 서로 다릅니다.
  • 환불 기능이 없습니다. 목표를 못 넘긴 펀딩의 토큰을 돌려받을 방법이 필요합니다.

8. 기술 스택

  • Solidity ^0.8.20
  • OpenZeppelin Contracts v5.x (ERC1155, ERC1155Holder, Ownable)
  • Remix IDE (개발 / 컴파일 / 배포)
  • ethers.js, web3.js (배포 스크립트)
  • IPFS (NFT 메타데이터 저장)

About

Smart Contract code of decentralized demand-response bus platform.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages